├── notebooks ├── files │ ├── cover_image.png │ └── wradlib_logo.svg.png ├── __init__.py ├── workflow │ ├── fig2_schwaller_morris_2011.png │ ├── fig3_schwaller_morris_2011.png │ ├── recipes.md │ ├── recipe2.md │ └── recipe1.md ├── basics │ ├── wradlib_introduction.md │ ├── basics.md │ ├── wradlib_in_an_hour.md │ └── wradlib_get_rainfall.md ├── georeferencing │ ├── georeferencing.md │ ├── coords.md │ └── georef.md ├── visualisation │ ├── plotting.md │ ├── plot_rhi.md │ ├── plot_ppi.md │ └── plot_scan_strategy.md ├── classify │ ├── classify.md │ ├── clutter_gabella.md │ ├── histo_cut.md │ ├── clutter_cloud.md │ ├── fuzzy_echo.md │ ├── hmcp_gpm.md │ ├── hmcp_nexrad.md │ └── 2d_hmc.md ├── fileio │ ├── backends │ │ ├── xarray_backends.md │ │ ├── radolan_backend.md │ │ ├── iris_backend.md │ │ ├── rainbow_backend.md │ │ ├── gamic_backend.md │ │ ├── cfradial2_backend.md │ │ ├── odim_backend.md │ │ ├── cfradial1_backend.md │ │ └── furuno_backend.md │ ├── fileio.md │ ├── legacy │ │ ├── legacy_readers.md │ │ ├── read_hdf5.md │ │ ├── read_gamic.md │ │ ├── read_iris.md │ │ ├── read_odim.md │ │ ├── read_rainbow.md │ │ ├── read_netcdf.md │ │ └── read_dx.md │ └── gis │ │ ├── raster_data.md │ │ └── vector_data.md ├── python │ ├── learnpython.md │ ├── numpyintro.md │ ├── mplintro.md │ └── timeseries.md ├── conftest.py ├── verification │ └── verification.md ├── interpolation │ └── interpolation.md ├── zonalstats │ └── zonalstats.md └── attenuation │ └── attenuation.md ├── .readthedocs.yaml ├── README.md ├── _includes └── license_block.md ├── environment.yml ├── .gitignore ├── LICENSE-CC-BY-SA-4.0 ├── LICENSE ├── LICENSE-MIT ├── index.md ├── .pre-commit-config.yaml ├── conf.py └── .github └── workflows └── main.yml /notebooks/files/cover_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wradlib/wradlib-notebooks/HEAD/notebooks/files/cover_image.png -------------------------------------------------------------------------------- /notebooks/files/wradlib_logo.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wradlib/wradlib-notebooks/HEAD/notebooks/files/wradlib_logo.svg.png -------------------------------------------------------------------------------- /notebooks/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2018-2025, wradlib developers. 2 | # Distributed under the MIT License. See LICENSE-MIT for more info. 3 | -------------------------------------------------------------------------------- /notebooks/workflow/fig2_schwaller_morris_2011.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wradlib/wradlib-notebooks/HEAD/notebooks/workflow/fig2_schwaller_morris_2011.png -------------------------------------------------------------------------------- /notebooks/workflow/fig3_schwaller_morris_2011.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wradlib/wradlib-notebooks/HEAD/notebooks/workflow/fig3_schwaller_morris_2011.png -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | build: 4 | os: ubuntu-24.04 5 | tools: 6 | python: mambaforge-latest 7 | 8 | conda: 9 | environment: environment.yml 10 | 11 | sphinx: 12 | builder: html 13 | configuration: conf.py 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # README # 2 | 3 | ## wradlib-notebooks ## 4 | 5 | This repository consists of jupyter notebooks written in [myst markdown](https://mystmd.org/) to give examples of wradlib usage and help users via tutorials. 6 | 7 | Rendered versions are available within the wradlib documentation hosted on [ReadTheDocs](https://docs.wradlib.org/projects/notebooks/en/latest/). 8 | -------------------------------------------------------------------------------- /_includes/license_block.md: -------------------------------------------------------------------------------- 1 | ```{raw} html 2 | 9 | ``` -------------------------------------------------------------------------------- /notebooks/basics/wradlib_introduction.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: md:myst 4 | text_representation: 5 | extension: .md 6 | format_name: myst 7 | kernelspec: 8 | name: python3 9 | display_name: Python 3 10 | --- 11 | 12 | ```{include} ../../_includes/license_block.md 13 | ``` 14 | # Import wradlib and check its version 15 | 16 | This simple example shows the $\omega radlib$ version at rendering time. 17 | 18 | ```{code-cell} python 19 | import wradlib 20 | 21 | print(wradlib.__version__) 22 | ``` 23 | -------------------------------------------------------------------------------- /environment.yml: -------------------------------------------------------------------------------- 1 | channels: 2 | - conda-forge 3 | - nodefaults 4 | dependencies: 5 | - python=3.13 6 | - bottleneck 7 | - cartopy 8 | - git 9 | - gdal 10 | - geopandas 11 | - ipykernel 12 | - jupytext 13 | - libgdal-hdf5 14 | - libgdal-netcdf 15 | - linkify-it-py 16 | - matplotlib-base 17 | - myst-nb 18 | - nbconvert 19 | - notebook 20 | - numpy 21 | - open-radar-data 22 | - pytest 23 | - pytest-sugar 24 | - pytest-xdist 25 | - requests 26 | - rioxarray 27 | - sphinx 28 | - sphinx-copybutton 29 | - sphinx-book-theme>=1.0 30 | - wradlib 31 | - xarray >=2024.10.0 32 | - pip: 33 | - wradlib_data -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything 2 | * 3 | 4 | # whitelist notebooks, png and shell 5 | !*.ipynb 6 | !*.png 7 | !*.sh 8 | 9 | # whitelist scripts 10 | !scripts/ 11 | !/scripts/* 12 | 13 | # whitelist notebooks 14 | !notebooks/ 15 | #!/notebooks/** 16 | # ignore .py (paired notebooks) 17 | notebooks/**/*.py 18 | 19 | # whitelist .dask 20 | !.dask/ 21 | !/.dask/* 22 | 23 | # whitelist ci 24 | !ci/ 25 | 26 | # whitelist binder 27 | !binder/ 28 | !/binder/* 29 | 30 | # Whitelist root files 31 | !.gitignore 32 | !conftest.py 33 | !README.md 34 | !LICENSE.txt 35 | !Welcome_Pangeo.md 36 | 37 | # blacklist standard notebook copies 38 | *-Copy*.ipynb 39 | 40 | # blacklist checkpoints 41 | *checkpoint* 42 | -------------------------------------------------------------------------------- /LICENSE-CC-BY-SA-4.0: -------------------------------------------------------------------------------- 1 | Creative Commons Attribution-ShareAlike 4.0 International License (CC BY-SA 4.0) 2 | Copyright (c) 2025 wradlib developers 3 | 4 | You are free to: 5 | • Share — copy and redistribute the material in any medium or format 6 | • Adapt — remix, transform, and build upon the material for any purpose, 7 | even commercially 8 | 9 | Under the following terms: 10 | • Attribution — give appropriate credit, provide a link to the license, 11 | and indicate if changes were made. 12 | • ShareAlike — if you remix, transform, or build upon the material, 13 | you must distribute your contributions under the same license. 14 | 15 | Full license text: https://creativecommons.org/licenses/by-sa/4.0/legalcode -------------------------------------------------------------------------------- /notebooks/basics/basics.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: md:myst 4 | text_representation: 5 | extension: .md 6 | format_name: myst 7 | kernelspec: 8 | name: python3 9 | display_name: Python 3 10 | --- 11 | 12 | ```{include} ../../_includes/license_block.md 13 | ``` 14 | # Getting started with wradlib 15 | 16 | This section provides a collection of example code snippets to make users familiar with $\omega radlib$. 17 | 18 | ## Examples List 19 | ```{toctree} 20 | :hidden: 21 | :maxdepth: 2 22 | Get wradlib version 23 | Typical Workflow for Radar-Based Rainfall Estimation 24 | From Reflectivity to Rainfall 25 | wradlib in an hour 26 | ``` 27 | -------------------------------------------------------------------------------- /notebooks/georeferencing/georeferencing.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: md:myst 4 | text_representation: 5 | extension: .md 6 | format_name: myst 7 | kernelspec: 8 | name: python3 9 | display_name: Python 3 10 | --- 11 | 12 | ```{include} ../../_includes/license_block.md 13 | ``` 14 | # Georeferencing 15 | 16 | 17 | This section provides a collection of example code snippets to show how georeferencing of radar and ancillary data is done in $\omega radlib$. 18 | 19 | ```{toctree} 20 | :hidden: 21 | :maxdepth: 2 22 | Computing Cartesian Coordinates from Polar Data 23 | Georeferencing a Radar Dataset 24 | Overlay Ancillary Data <../visualisation/gis_overlay> 25 | Managing Georeferenced Data <../zonalstats/zonalstats> 26 | ``` -------------------------------------------------------------------------------- /notebooks/visualisation/plotting.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: md:myst 4 | text_representation: 5 | extension: .md 6 | format_name: myst 7 | kernelspec: 8 | name: python3 9 | display_name: Python 3 10 | --- 11 | 12 | ```{include} ../../_includes/license_block.md 13 | ``` 14 | # Plotting with wradlib 15 | 16 | 17 | This section provides a collection of example code snippets to make users familiar with $\omega radlib$'s plotting capabilities. 18 | 19 | ## Examples List 20 | ```{toctree} 21 | :hidden: 22 | :maxdepth: 2 23 | Plot PPI 24 | Plot RHI 25 | Plot Curvelinear Grids 26 | Plot geodata 27 | Plot geodata with cartopy 28 | Plot radar scan strategy 29 | ``` 30 | -------------------------------------------------------------------------------- /notebooks/classify/classify.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: md:myst 4 | text_representation: 5 | extension: .md 6 | format_name: myst 7 | kernelspec: 8 | name: python3 9 | display_name: Python 3 10 | --- 11 | 12 | ```{include} ../../_includes/license_block.md 13 | ``` 14 | # Clutter and Echo Classification 15 | 16 | 17 | This section provides a collection of example code snippets to show clutter detection and correction as well as echo classification capabilities of $\omega radlib$. 18 | 19 | 20 | ## Examples List 21 | ```{toctree} 22 | :hidden: 23 | :maxdepth: 2 24 | Clutter detection using Gabella approach 25 | Clutter detection by using space-born cloud images 26 | Heuristic clutter detection 27 | Fuzzy echo classification 28 | 2D Membershipfunction HMC <2d_hmc> 29 | Hydrometeor partitioning ratio - GPM 30 | Hydrometeor partitioning ratio - GroundRadar 31 | ``` 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | # Licensing Overview 2 | 3 | This repository contains three categories of content under distinct licenses. 4 | 5 | 1. **Source code files** (`.py` files) are licensed under the 6 | MIT License. See `LICENSE-MIT` for the full text. 7 | 8 | 2. **Documentation and notebooks** (e.g. `.md`, `.myst`, and rendered HTML files), 9 | including code cells within notebooks are licensed under the Creative Commons 10 | Attribution–ShareAlike 4.0 International License (CC BY-SA 4.0). See 11 | `LICENSE-CC-BY-SA-4.0` for the full text. 12 | 13 | 3. **Data files** are licensed under their respective license conditions 14 | but at least Creative Commons Attribution 4.0 International License (CC BY 4.0), 15 | unless otherwise noted. See `ATTRIBUTION.md`. 16 | 17 | When redistributing this repository in whole or in part, please retain all license 18 | files and notices. The applicable license for each file or directory is the one 19 | stated within or nearest to it. 20 | 21 | 22 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2025 wradlib developers 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | 11 | -------------------------------------------------------------------------------- /index.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: md:myst 4 | text_representation: 5 | extension: .md 6 | format_name: myst 7 | kernelspec: 8 | name: python3 9 | display_name: Python 3 10 | --- 11 | 12 | ```{include} _includes/license_block.md 13 | ``` 14 | 15 | 16 | # Tutorials and Examples 17 | 18 | This section provides a collection of example code snippets to make users familiar with $\omega radlib$ and weather radar data processing. 19 | 20 | ```{toctree} 21 | :maxdepth: 1 22 | Python Intro 23 | wradlib Basics 24 | Data Input/Output 25 | Visualization 26 | Attenuation Correction 27 | Beam Blockage 28 | Echo Classification 29 | Georeferencing 30 | Interpolation 31 | Gauge Adjustment 32 | Verification 33 | Zonal Statistics 34 | Recipes 35 | RADOLAN Guide 36 | ``` -------------------------------------------------------------------------------- /notebooks/fileio/backends/xarray_backends.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: md:myst 4 | text_representation: 5 | extension: .md 6 | format_name: myst 7 | kernelspec: 8 | name: python3 9 | display_name: Python 3 10 | --- 11 | 12 | ```{include} ../../../_includes/license_block.md 13 | ``` 14 | # xarray backends 15 | 16 | 17 | This section gives an overview on the available xarray backends. 18 | 19 | 20 | 21 | ```{warning} 22 | For radar data in polar format the [openradar community](https://openradarscience.org/) published [xradar](https://docs.openradarscience.org/projects/xradar/en/latest/) where xarray-based readers/writers are implemented. That particular code was ported from $\omega radlib$ to xradar. Please refer to xradar for enhancements for polar radar. 23 | 24 | Since $\omega radlib$ version 1.19 almost all backend code has been transferred to [xradar](https://github.com/openradar/xradar)-package and is imported from there. 25 | ``` 26 | 27 | ```{toctree} 28 | :hidden: 29 | :maxdepth: 1 30 | RADOLAN 31 | ODIM 32 | GAMIC 33 | CfRadial1 34 | CfRadial2 35 | Iris/Sigmet 36 | Rainbow5 37 | Furuno SCN/SCNX 38 | ``` -------------------------------------------------------------------------------- /notebooks/fileio/fileio.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: md:myst 4 | text_representation: 5 | extension: .md 6 | format_name: myst 7 | kernelspec: 8 | name: python3 9 | display_name: Python 3 10 | --- 11 | 12 | ```{include} ../../_includes/license_block.md 13 | ``` 14 | # Data Input - Data Output 15 | 16 | 17 | The binary encoding of many radar products is a major obstacle for many potential radar users. Often, decoder software is not easily available. In case formats are documented, the implementation of decoders is a major programming effort. 18 | 19 | ```{note} 20 | For radar data in polar format (eg. rays vs. range) the [openradar community](https://openradarscience.org/) published [xradar](https://docs.openradarscience.org/projects/xradar/en/latest/) where xarray-based readers/writers are implemented. That particular code was ported from $\omega radlib$ to xradar. Please refer to xradar for enhancements for polar radar. 21 | ``` 22 | 23 | This section provides a collection of example code snippets to show which data formats $\omega radlib$ can handle and and how to facilitate that. 24 | 25 | ```{toctree} 26 | :hidden: 27 | :maxdepth: 2 28 | Legacy readers 29 | Xarray readers 30 | GIS Vector Data 31 | GIS Raster Export 32 | ``` -------------------------------------------------------------------------------- /notebooks/classify/clutter_gabella.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: md:myst 4 | text_representation: 5 | extension: .md 6 | format_name: myst 7 | format_version: 0.13 8 | jupytext_version: 1.18.1 9 | kernelspec: 10 | name: python3 11 | display_name: Python 3 12 | --- 13 | 14 | ```{include} ../../_includes/license_block.md 15 | ``` 16 | # Clutter detection using the Gabella approach 17 | 18 | ```{code-cell} python 19 | import warnings 20 | 21 | import matplotlib.pyplot as plt 22 | import numpy as np 23 | import wradlib as wrl 24 | import wradlib_data 25 | 26 | warnings.filterwarnings("ignore") 27 | ``` 28 | 29 | ## Read the data 30 | 31 | ```{code-cell} python 32 | filename = wradlib_data.DATASETS.fetch("misc/polar_dBZ_fbg.gz") 33 | data = np.loadtxt(filename) 34 | data = wrl.georef.create_xarray_dataarray(data, rf=0.001).wrl.georef.georeference() 35 | data 36 | ``` 37 | 38 | ## Apply filter 39 | 40 | ```{code-cell} python 41 | clmap = data.wrl.classify.filter_gabella( 42 | wsize=5, thrsnorain=0.0, tr1=6.0, n_p=8, tr2=1.3 43 | ) 44 | clmap 45 | ``` 46 | 47 | ## Plot results 48 | 49 | ```{code-cell} python 50 | fig = plt.figure(figsize=(12, 8)) 51 | ax1 = fig.add_subplot(121) 52 | pm = data.wrl.vis.plot(ax=ax1) 53 | ax1.set_title("Reflectivity") 54 | ax2 = fig.add_subplot(122) 55 | pm = clmap.wrl.vis.plot(ax=ax2) 56 | ax2.set_title("Cluttermap") 57 | ``` 58 | -------------------------------------------------------------------------------- /notebooks/fileio/legacy/legacy_readers.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: md:myst 4 | text_representation: 5 | extension: .md 6 | format_name: myst 7 | kernelspec: 8 | name: python3 9 | display_name: Python 3 10 | --- 11 | 12 | ```{include} ../../../_includes/license_block.md 13 | ``` 14 | # Legacy readers 15 | 16 | ```{warning} 17 | For radar data in polar format the [openradar community](https://openradarscience.org/) published [xradar](https://docs.openradarscience.org/projects/xradar/en/latest/) where xarray-based readers/writers are implemented. That particular code was ported from $\omega radlib$ to xradar. Please refer to xradar for enhancements for polar radar. 18 | 19 | From $\omega radlib$ version 1.19 that functionality is imported from [xradar](https://github.com/openradar/xradar)-package whenever and wherever necessary. 20 | 21 | Please refer to [xradar based examples](../backends/xarray_backends) for an introduction. 22 | ``` 23 | 24 | Since new developments are done in xradar this chapter only covers the legacy readers with numpy output. They will work fine, but we recommend to use the available xarray based readers. 25 | 26 | Reading weather radar files is done via the {mod}`wradlib.io` module. There you will find a complete function reference. 27 | 28 | ```{toctree} 29 | :hidden: 30 | :maxdepth: 2 31 | DWD DX 32 | NetCDF 33 | HDF5 34 | OPERA ODIM 35 | GAMIC HDF 36 | Leonardo Rainbow 37 | Vaisala IRIS/Sigmet 38 | ``` -------------------------------------------------------------------------------- /notebooks/fileio/legacy/read_hdf5.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: md:myst 4 | text_representation: 5 | extension: .md 6 | format_name: myst 7 | kernelspec: 8 | name: python3 9 | display_name: Python 3 10 | --- 11 | 12 | ```{include} ../../../_includes/license_block.md 13 | ``` 14 | # Reading HDF5 files with a generic reader 15 | 16 | This reader utilizes [h5py](https://docs.h5py.org). 17 | 18 | In this example, we read HDF5 files from different sources using a generic reader from $\omega radlib's$ io module. 19 | 20 | ```{code-cell} python 21 | import wradlib as wrl 22 | import wradlib_data 23 | import matplotlib.pyplot as plt 24 | import warnings 25 | import numpy as np 26 | warnings.filterwarnings("ignore") 27 | 28 | ``` 29 | 30 | This is a generic hdf5 reader, which will read any hdf5 structure. 31 | 32 | ```{code-cell} python 33 | fpath = "hdf5/2014-08-10--182000.ppi.mvol" 34 | f = wradlib_data.DATASETS.fetch(fpath) 35 | fcontent = wrl.io.read_generic_hdf5(f) 36 | ``` 37 | 38 | ```{code-cell} python 39 | print(fcontent.keys()) 40 | ``` 41 | 42 | ```{code-cell} python 43 | print(fcontent["where"]) 44 | print(fcontent["how"]) 45 | print(fcontent["scan0/moment_3"].keys()) 46 | print(fcontent["scan0/moment_3"]["attrs"]) 47 | print(fcontent["scan0/moment_3"]["data"].shape) 48 | ``` 49 | 50 | ```{code-cell} python 51 | fig = plt.figure(figsize=(10, 10)) 52 | da = wrl.georef.create_xarray_dataarray( 53 | fcontent["scan0/moment_3"]["data"] 54 | ).wrl.georef.georeference() 55 | im = da.wrl.vis.plot(fig=fig, crs="cg") 56 | ``` -------------------------------------------------------------------------------- /notebooks/python/learnpython.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: md:myst 4 | text_representation: 5 | extension: .md 6 | format_name: myst 7 | kernelspec: 8 | name: python3 9 | display_name: Python 3 10 | --- 11 | 12 | ```{include} ../../_includes/license_block.md 13 | ``` 14 | # An incomplete introduction to Python 15 | 16 | 17 | In order to use $\omega radlib$, you need to be able to use Python. The notebooks in this section will give you a glimpse of the Python language and important scientific packages, confront you with some Python exercises, and refer you to further online material. In addition, we will address some issues that are relevant to $\omega radlib$, but do not necessarily address $\omega radlib$ functionality. 18 | 19 | As an introduction to scientific python, **this section is necessarily incomplete**. There are better and more comprehensive courses such as the [SciPy Lecture Notes](https://scipy-lectures.org/index.html) or the [Dive into Python](https://en.wikipedia.org/wiki/Mark_Pilgrim#Dive_into_Python) book. 20 | 21 | Mostly, we will assume that you have some basic knowledge about programming, e.g. from other interpreted languages such as [R](https://www.r-project.org/) or Matlab. Otherwise, a fundamental course such as [learnpython.org](https://www.learnpython.org/) might help you more. 22 | 23 | ```{toctree} 24 | :hidden: 25 | :maxdepth: 2 26 | A quick start to Python 27 | Numpy: manipulating numerical data 28 | Visualising data with Matplotlib 29 | Dealing with time series 30 | ``` -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | # --- Jupytext: create temporary .py mirrors from MyST notebooks --- 3 | - repo: https://github.com/mwouts/jupytext 4 | rev: v1.19.0.dev0 5 | hooks: 6 | - id: jupytext 7 | name: jupytext (md → py) 8 | files: "^notebooks/.*\\.md$" 9 | args: ["--to", "py:percent"] 10 | pass_filenames: false 11 | always_run: true 12 | 13 | # --- Ruff --- 14 | - repo: https://github.com/astral-sh/ruff-pre-commit 15 | rev: v0.14.6 16 | hooks: 17 | - id: ruff 18 | name: ruff (lint + fix) 19 | files: "^notebooks/.*\\.py$" 20 | args: ["--fix"] 21 | 22 | # --- Black --- 23 | - repo: https://github.com/psf/black 24 | rev: 25.11.0 25 | hooks: 26 | - id: black 27 | files: "^notebooks/.*\\.py$" 28 | 29 | # --- Jupytext: sync py → md (only paired mirrors) --- 30 | - repo: https://github.com/mwouts/jupytext 31 | rev: v1.19.0.dev0 32 | hooks: 33 | - id: jupytext 34 | name: jupytext (sync py → md) 35 | files: '^notebooks/(?!__init__\\.py$)(?!conftest\\.py$).*\\.py$' 36 | args: ["--sync"] 37 | # DO NOT set pass_filenames: false 38 | always_run: false 39 | 40 | # --- Cleanup temporary .py files --- 41 | - repo: local 42 | hooks: 43 | - id: cleanup-jupytext 44 | name: Remove temporary Jupytext Python mirrors 45 | entry: bash -c "find notebooks -type f -name '*.py' ! -name '__init__.py' ! -name 'conftest.py' -exec rm -f {} +" 46 | language: system 47 | always_run: true 48 | -------------------------------------------------------------------------------- /notebooks/workflow/recipes.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: md:myst 4 | text_representation: 5 | extension: .md 6 | format_name: myst 7 | kernelspec: 8 | name: python3 9 | display_name: Python 3 10 | --- 11 | 12 | ```{include} ../../_includes/license_block.md 13 | ``` 14 | # Recipes 15 | 16 | ```{toctree} 17 | :hidden: 18 | :maxdepth: 2 19 | Recipe1: Clutter and attenuation correction plus composition for two DWD radars 20 | Recipe2: Reading and visualizing an ODIM_H5 polar volume 21 | Recipe3: Match spaceborn SR (GPM/TRRM) with ground radars GR 22 | Recipe4: Load ODIM_H5 Volume data from DWD Open Data 23 | Recipe5: Zonalstats on Cartesian Grid 24 | Recipe6: Zonalstats on Polar Grid 25 | ``` 26 | 27 | This recipe section provides a collection of code snippets for exemplary $\omega radlib$ applications. Compared to the other notebooks, the level of documentation is a bit lower. For each recipe, we provide a short summary and a link to the example code. 28 | 29 | Please feel to send us your recipes so we can include them on this page. Please open an issue at the [wradlib_github issue tracker](https://github.com/wradlib/wradlib) containing: 30 | 31 | - *optional*: name and affiliation 32 | 33 | - a suggested title of the recipe 34 | 35 | - a short description of the recipe (max. 100 words) 36 | 37 | - the recipe code (please add comments within the code!) 38 | 39 | - *optional*: the data needed to run the code (or a hyperlink to the data) 40 | 41 | - *optional*: some test output of the recipe (e.g. an image file) 42 | -------------------------------------------------------------------------------- /notebooks/georeferencing/coords.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: md:myst 4 | text_representation: 5 | extension: .md 6 | format_name: myst 7 | format_version: 0.13 8 | jupytext_version: 1.18.1 9 | kernelspec: 10 | name: python3 11 | display_name: Python 3 12 | --- 13 | 14 | ```{include} ../../_includes/license_block.md 15 | ``` 16 | # Computing cartesian and geographical coordinates for polar data 17 | 18 | ```{code-cell} python 19 | import warnings 20 | 21 | import wradlib as wrl 22 | import wradlib_data 23 | import xradar as xd 24 | from IPython.display import display 25 | 26 | warnings.filterwarnings("ignore") 27 | ``` 28 | 29 | ## Read the data 30 | 31 | Here, we use an OPERA hdf5 dataset. 32 | 33 | ```{code-cell} python 34 | filename = "hdf5/20130429043000.rad.bewid.pvol.dbzh.scan1.hdf" 35 | filename = wradlib_data.DATASETS.fetch(filename) 36 | pvol = xd.io.open_odim_datatree(filename) 37 | display(pvol) 38 | ``` 39 | 40 | ## Retrieve azimuthal equidistant coordinates and projection 41 | 42 | ```{code-cell} python 43 | for key in list(pvol.children): 44 | if "sweep" in key: 45 | pvol[key].ds = pvol[key].ds.wrl.georef.georeference() 46 | ``` 47 | 48 | ```{code-cell} python 49 | pvol["sweep_0"].ds.DBZH.plot(x="x", y="y") 50 | ``` 51 | 52 | ## Retrieve geographic coordinates (longitude and latitude) 53 | 54 | 55 | ### Using crs-keyword argument. 56 | 57 | ```{code-cell} python 58 | for key in list(pvol.children): 59 | if "sweep" in key: 60 | pvol[key].ds = pvol[key].ds.wrl.georef.georeference( 61 | crs=wrl.georef.get_default_projection() 62 | ) 63 | ``` 64 | 65 | ```{code-cell} python 66 | ds1 = pvol["sweep_0"].ds.wrl.georef.georeference( 67 | crs=wrl.georef.get_default_projection() 68 | ) 69 | ds1.DBZH.plot(x="x", y="y") 70 | ``` 71 | 72 | ### Using reproject 73 | 74 | ```{code-cell} python 75 | ds2 = pvol["sweep_0"].ds.wrl.georef.reproject( 76 | trg_crs=wrl.georef.epsg_to_osr(32632), 77 | ) 78 | ds2.DBZH.plot(x="x", y="y") 79 | ``` 80 | -------------------------------------------------------------------------------- /notebooks/fileio/backends/radolan_backend.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: md:myst 4 | text_representation: 5 | extension: .md 6 | format_name: myst 7 | kernelspec: 8 | name: python3 9 | display_name: Python 3 10 | --- 11 | 12 | ```{include} ../../../_includes/license_block.md 13 | ``` 14 | # xarray RADOLAN backend 15 | 16 | In this example, we read RADOLAN data files using the xarray `radolan` backend. 17 | 18 | ```{code-cell} python 19 | import glob 20 | import os 21 | import wradlib as wrl 22 | import wradlib_data 23 | import warnings 24 | from IPython.display import display 25 | 26 | import matplotlib.pyplot as plt 27 | import numpy as np 28 | import xarray as xr 29 | 30 | warnings.filterwarnings("ignore") 31 | 32 | ``` 33 | 34 | ## Load RADOLAN Data 35 | 36 | ```{code-cell} python 37 | fpath = "radolan/misc/raa01-rw_10000-1408030950-dwd---bin.gz" 38 | f = wradlib_data.DATASETS.fetch(fpath) 39 | comp = wrl.io.open_radolan_dataset(f) 40 | ``` 41 | 42 | ## Inspect Data 43 | 44 | ```{code-cell} python 45 | display(comp) 46 | ``` 47 | 48 | ## Inspect RADOLAN moments 49 | 50 | The DataArrays can be accessed by key or by attribute. Each DataArray has dimensions and coordinates of its parent dataset. 51 | 52 | ```{code-cell} python 53 | display(comp.RW) 54 | ``` 55 | 56 | ## Create simple plot 57 | 58 | Using xarray features a simple plot can be created like this. 59 | 60 | ```{code-cell} python 61 | comp.RW.plot(x="x", y="y", add_labels=False) 62 | ``` 63 | 64 | ```{code-cell} python 65 | fig = plt.figure(figsize=(10, 10)) 66 | ax = fig.add_subplot(111) 67 | comp.RW.plot(x="x", y="y", ax=ax) 68 | ``` 69 | 70 | ## Mask some values 71 | 72 | ```{code-cell} python 73 | ds = comp["RW"].where(comp["RW"] >= 1) 74 | ds.plot() 75 | ``` 76 | 77 | ## Use `xr.open_dataset` 78 | 79 | 80 | ```{code-cell} python 81 | comp2 = xr.open_dataset(f, engine="radolan") 82 | display(comp2) 83 | ``` 84 | 85 | ## Use `xr.open_mfdataset` to retrieve timeseries 86 | 87 | ```{code-cell} python 88 | flist = [ 89 | "radolan/misc/raa01-sf_10000-1305270050-dwd---bin.gz", 90 | "radolan/misc/raa01-sf_10000-1305280050-dwd---bin.gz", 91 | ] 92 | flist = [wradlib_data.DATASETS.fetch(f) for f in flist] 93 | ``` 94 | 95 | ```{code-cell} python 96 | comp3 = xr.open_mfdataset(flist, engine="radolan") 97 | display(comp3) 98 | ``` 99 | -------------------------------------------------------------------------------- /notebooks/fileio/legacy/read_gamic.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: md:myst 4 | text_representation: 5 | extension: .md 6 | format_name: myst 7 | kernelspec: 8 | name: python3 9 | display_name: Python 3 10 | --- 11 | 12 | ```{include} ../../../_includes/license_block.md 13 | ``` 14 | # GAMIC HDF5 15 | 16 | ```{code-cell} python 17 | import wradlib as wrl 18 | import wradlib_data 19 | import matplotlib.pyplot as plt 20 | import numpy as np 21 | import warnings 22 | 23 | warnings.filterwarnings("ignore") 24 | ``` 25 | 26 | GAMIC refers to the commercial [GAMIC Enigma MURAN software](https://www.gamic.com) which exports data in hdf5 format. The concept is quite similar to the {ref}`notebooks/fileio/legacy/read_odim:opera hdf5 (odim_h5)` format. 27 | 28 | ```{warning} 29 | For radar data in GAMIC HDF5 format the [openradar community](https://openradarscience.org/) published [xradar](https://docs.openradarscience.org/projects/xradar/en/latest/) where xarray-based readers/writers are implemented. That particular code was ported from $\omega radlib$ to xradar. Please refer to xradar for enhancements for polar radar. 30 | 31 | From $\omega radlib$ version 1.19 `GAMIC HDF5` reading code is imported from [xradar](https://github.com/openradar/xradar)-package whenever and wherever necessary. 32 | 33 | Please read the more indepth notebook [gamic_backend](../backends/gamic_backend). 34 | ``` 35 | 36 | Such a file (typical ending: *.mvol*) can be read by: 37 | 38 | ```{code-cell} python 39 | fpath = "hdf5/2014-08-10--182000.ppi.mvol" 40 | f = wradlib_data.DATASETS.fetch(fpath) 41 | data, metadata = wrl.io.read_gamic_hdf5(f) 42 | ``` 43 | 44 | While metadata represents the usual dictionary of metadata, the data variable is a dictionary which might contain several numpy arrays with the keywords of the dictionary indicating different moments. 45 | 46 | ```{code-cell} python 47 | print(metadata.keys()) 48 | print(metadata["VOL"]) 49 | print(metadata["SCAN0"].keys()) 50 | ``` 51 | 52 | ```{code-cell} python 53 | print(data["SCAN0"].keys()) 54 | print(data["SCAN0"]["PHIDP"].keys()) 55 | print(data["SCAN0"]["PHIDP"]["data"].shape) 56 | ``` 57 | 58 | ```{code-cell} python 59 | fig = plt.figure(figsize=(10, 10)) 60 | da = wrl.georef.create_xarray_dataarray( 61 | data["SCAN0"]["ZH"]["data"] 62 | ).wrl.georef.georeference() 63 | im = da.wrl.vis.plot(fig=fig, crs="cg") 64 | ``` -------------------------------------------------------------------------------- /notebooks/conftest.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: UTF-8 -*- 3 | # Copyright (c) 2025, wradlib developers. 4 | # Distributed under the MIT License. See LICENSE-MIT for more info. 5 | 6 | 7 | from pathlib import Path 8 | 9 | import jupytext 10 | import nbformat 11 | import pytest 12 | from nbclient import NotebookClient 13 | from nbclient.exceptions import CellExecutionError 14 | 15 | 16 | def pytest_configure(config): 17 | config.addinivalue_line("norecursedirs", "_build") 18 | 19 | 20 | def pytest_collect_file(parent, file_path: Path): 21 | if file_path.suffix in (".md",): 22 | return NotebookFile.from_parent(parent=parent, path=file_path) 23 | 24 | 25 | class NotebookFile(pytest.File): 26 | @classmethod 27 | def from_parent(cls, parent, path): 28 | return super().from_parent(parent=parent, path=path) 29 | 30 | def collect(self): 31 | yield NotebookItem.from_parent(parent=self, name=self.path.name) 32 | 33 | 34 | class NotebookItem(pytest.Item): 35 | def __init__(self, name, parent): 36 | super().__init__(name, parent) 37 | 38 | def runtest(self): 39 | # Load notebook (MyST .md or .ipynb) 40 | if self.parent.path.suffix == ".md": 41 | nb = jupytext.read(self.parent.path) 42 | 43 | # ensure NotebookNode 44 | if not isinstance(nb, nbformat.NotebookNode): 45 | nb = nbformat.from_dict(nb) 46 | 47 | # ensure kernel info 48 | nb.metadata.setdefault( 49 | "kernelspec", 50 | {"name": "python3", "display_name": "Python 3", "language": "python"}, 51 | ) 52 | 53 | # clear previous outputs, if any 54 | for cell in nb.cells: 55 | if cell.cell_type == "code": 56 | cell.outputs = [] 57 | cell.execution_count = None 58 | 59 | # execute notebook 60 | client = NotebookClient( 61 | nb, 62 | kernel_name="python3", 63 | timeout=600, 64 | iopub_timeout=600, 65 | allow_errors=False, 66 | ) 67 | try: 68 | client.execute() 69 | except CellExecutionError as e: 70 | raise NotebookException(e) from e 71 | 72 | def repr_failure(self, excinfo): 73 | if isinstance(excinfo.value, NotebookException): 74 | return excinfo.exconly() 75 | return super().repr_failure(excinfo) 76 | 77 | def reportinfo(self): 78 | return self.parent.path, 0, f"TestCase: {self.name}" 79 | 80 | 81 | class NotebookException(Exception): 82 | pass 83 | -------------------------------------------------------------------------------- /notebooks/classify/histo_cut.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: md:myst 4 | text_representation: 5 | extension: .md 6 | format_name: myst 7 | format_version: 0.13 8 | jupytext_version: 1.18.1 9 | kernelspec: 10 | name: python3 11 | display_name: Python 3 12 | --- 13 | 14 | ```{include} ../../_includes/license_block.md 15 | ``` 16 | # Heuristic clutter detection based on distribution properties ("histo cut") 17 | 18 | Detects areas with anomalously low or high average reflectivity or precipitation. It is recommended to use long term average or sums (months to year). 19 | 20 | ```{code-cell} python 21 | import warnings 22 | 23 | import matplotlib.pyplot as plt 24 | import numpy as np 25 | import wradlib as wrl 26 | import wradlib_data 27 | 28 | warnings.filterwarnings("ignore") 29 | ``` 30 | 31 | ## Load annual rainfall accumulation example (from DWD radar Feldberg) 32 | 33 | ```{code-cell} python 34 | filename = wradlib_data.DATASETS.fetch("misc/annual_rainfall_fbg.gz") 35 | yearsum = np.loadtxt(filename) 36 | ``` 37 | 38 | ## Apply histo-cut filter to retrieve boolean array that highlights clutter as well as beam blockage 39 | 40 | Depending on your data and climate you can parameterize the upper and lower frequency percentage with the kwargs `upper_frequency`/`lower_frequency`. For European ODIM_H5 data these values have been found to be in the order of 0.05 in [EURADCLIM: The European climatological high-resolution gauge-adjusted radar precipitation dataset](https://essd.copernicus.org/preprints/essd-2022-334/). The current default is 0.01 for both values. 41 | 42 | ```{code-cell} python 43 | mask = wrl.classify.histo_cut(yearsum) 44 | ``` 45 | 46 | ## Plot results 47 | 48 | ```{code-cell} python 49 | fig = plt.figure(figsize=(14, 14)) 50 | ax = fig.add_subplot(221) 51 | yearsum = wrl.georef.create_xarray_dataarray(yearsum).wrl.georef.georeference() 52 | pm = np.log(yearsum).wrl.vis.plot(ax=ax) 53 | plt.title("Logarithm of annual precipitation sum") 54 | plt.colorbar(pm, shrink=0.75) 55 | ax = fig.add_subplot(222) 56 | mask = wrl.georef.create_xarray_dataarray(mask).wrl.georef.georeference() 57 | pm = mask.wrl.vis.plot(ax=ax) 58 | plt.title("Map of execptionally low and high values\n(clutter and beam blockage)") 59 | plt.colorbar(pm, shrink=0.75) 60 | ax = fig.add_subplot(223) 61 | pm = mask.where(mask == 1).wrl.vis.plot(ax=ax) 62 | plt.title("Map of execptionally high values\n(clutter)") 63 | plt.colorbar(pm, shrink=0.75) 64 | ax = fig.add_subplot(224) 65 | pm = mask.where(mask == 2).wrl.vis.plot(ax=ax) 66 | plt.title("Map of execptionally low values\n(beam blockage)") 67 | plt.colorbar(pm, shrink=0.75) 68 | ``` 69 | -------------------------------------------------------------------------------- /notebooks/fileio/legacy/read_iris.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: md:myst 4 | text_representation: 5 | extension: .md 6 | format_name: myst 7 | kernelspec: 8 | name: python3 9 | display_name: Python 3 10 | --- 11 | 12 | ```{include} ../../../_includes/license_block.md 13 | ``` 14 | # Vaisala Sigmet IRIS 15 | 16 | ```{code-cell} python 17 | import wradlib as wrl 18 | import wradlib_data 19 | import matplotlib.pyplot as plt 20 | import numpy as np 21 | import warnings 22 | 23 | 24 | warnings.filterwarnings("ignore") 25 | ``` 26 | 27 | [IRIS](https://www.vaisala.com/en/products/iris-focus-remote-sensing-software) refers to the commercial Vaisala Sigmet **I**nteractive **R**adar **I**nformation **S**ystem. The Vaisala Sigmet Digital Receivers export data in a [well documented](ftp://ftp.sigmet.com/outgoing/manuals/IRIS_Programmers_Manual.pdf) binary format. 28 | 29 | The philosophy behind the $\omega radlib$ interface to the IRIS data model is very straightforward: $\omega radlib$ simply translates the complete binary file structure to *one* dictionary and returns this dictionary to the user. Thus, the potential complexity of the stored data is kept and it is left to the user how to proceed with this data. The keys of the output dictionary are strings that correspond to the Sigmet Data Structures. 30 | 31 | 32 | ```{warning} 33 | For radar data in IRIS/Sigmet format the [openradar community](https://openradarscience.org/) published [xradar](https://docs.openradarscience.org/projects/xradar/en/latest/) where xarray-based readers/writers are implemented. That particular code was ported from $\omega radlib$ to xradar. Please refer to xradar for enhancements for polar radar. 34 | 35 | From $\omega radlib$ version 1.19 `IRIS/Sigmet` reading code is imported from [xradar](https://github.com/openradar/xradar)-package whenever and wherever necessary. 36 | 37 | Please read the more indepth notebook [iris_backend](../backends/iris_backend). 38 | ``` 39 | 40 | Such a file (typical ending: *.RAWXXXX) can be read by: 41 | 42 | ```{code-cell} python 43 | fpath = "sigmet/cor-main131125105503.RAW2049" 44 | f = wradlib_data.DATASETS.fetch(fpath) 45 | fcontent = wrl.io.read_iris(f) 46 | ``` 47 | 48 | ```{code-cell} python 49 | # which keywords can be used to access the content? 50 | print(fcontent.keys()) 51 | # print the entire content including values of data and 52 | # metadata of the first sweep 53 | # (numpy arrays will not be entirely printed) 54 | print(fcontent["data"][1].keys()) 55 | print() 56 | print(fcontent["data"][1]["ingest_data_hdrs"].keys()) 57 | print(fcontent["data"][1]["ingest_data_hdrs"]["DB_DBZ"]) 58 | print() 59 | print(fcontent["data"][1]["sweep_data"].keys()) 60 | print(fcontent["data"][1]["sweep_data"]["DB_DBZ"]) 61 | ``` 62 | 63 | ```{code-cell} python 64 | fig = plt.figure(figsize=(10, 10)) 65 | swp = fcontent["data"][1]["sweep_data"] 66 | da = wrl.georef.create_xarray_dataarray( 67 | swp["DB_DBZ"], 68 | ).wrl.georef.georeference() 69 | im = da.wrl.vis.plot(fig=fig, crs="cg") 70 | ``` 71 | -------------------------------------------------------------------------------- /notebooks/visualisation/plot_rhi.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: md:myst 4 | text_representation: 5 | extension: .md 6 | format_name: myst 7 | format_version: 0.13 8 | jupytext_version: 1.18.1 9 | kernelspec: 10 | name: python3 11 | display_name: Python 3 12 | --- 13 | 14 | ```{include} ../../_includes/license_block.md 15 | ``` 16 | # Quick-view a RHI sweep in polar or cartesian reference systems 17 | 18 | ```{code-cell} python 19 | import warnings 20 | 21 | import matplotlib.pyplot as plt 22 | import numpy as np 23 | import wradlib as wrl 24 | import wradlib_data 25 | 26 | warnings.filterwarnings("ignore") 27 | ``` 28 | 29 | ## Read a RHI polar data set from University Bonn XBand radar 30 | 31 | ```{code-cell} python 32 | filename = wradlib_data.DATASETS.fetch("hdf5/2014-06-09--185000.rhi.mvol") 33 | data1, metadata = wrl.io.read_gamic_hdf5(filename) 34 | img = data1["SCAN0"]["ZH"]["data"] 35 | # mask data array for better presentation 36 | mask_ind = np.where(img <= np.nanmin(img)) 37 | img[mask_ind] = np.nan 38 | img = np.ma.array(img, mask=np.isnan(img)) 39 | 40 | r = metadata["SCAN0"]["r"] 41 | th = metadata["SCAN0"]["el"] 42 | print(th.shape) 43 | az = metadata["SCAN0"]["az"] 44 | site = ( 45 | metadata["VOL"]["Longitude"], 46 | metadata["VOL"]["Latitude"], 47 | metadata["VOL"]["Height"], 48 | ) 49 | img = wrl.georef.create_xarray_dataarray( 50 | img, r=r, phi=az, theta=th, site=site, dim0="elevation", sweep_mode="rhi" 51 | ) 52 | img 53 | ``` 54 | 55 | Inspect the data set a little 56 | 57 | ```{code-cell} python 58 | print("Shape of polar array: %r\n" % (img.shape,)) 59 | print("Some meta data of the RHI file:") 60 | print("\tdatetime: %r" % (metadata["SCAN0"]["Time"],)) 61 | ``` 62 | 63 | ## The simplest way to plot this dataset 64 | 65 | ```{code-cell} python 66 | img = img.wrl.georef.georeference() 67 | pm = img.wrl.vis.plot() 68 | txt = plt.title("Simple RHI - Rays/Bins") 69 | # plt.gca().set_xlim(0,100000) 70 | # plt.gca().set_ylim(0,100000) 71 | ``` 72 | 73 | ```{code-cell} python 74 | pm = img.wrl.vis.plot() 75 | plt.gca().set_ylim(0, 15000) 76 | txt = plt.title("Simple RHI - Rays/Bins - with ylimits") 77 | ``` 78 | 79 | ```{code-cell} python 80 | pm = img.wrl.vis.plot(crs="cg") 81 | plt.gca().set_title("Curvelineart Grid RHI", y=1.0, pad=20) 82 | ``` 83 | 84 | ## More decorations and annotations 85 | 86 | You can annotate these plots by using standard matplotlib methods. 87 | 88 | ```{code-cell} python 89 | pm = img.wrl.vis.plot() 90 | ax = plt.gca() 91 | ylabel = ax.set_xlabel("Ground Range [m]") 92 | ylabel = ax.set_ylabel("Height [m]") 93 | title = ax.set_title("RHI manipulations/colorbar", y=1, pad=20) 94 | # you can now also zoom - either programmatically or interactively 95 | xlim = ax.set_xlim(25000, 40000) 96 | ylim = ax.set_ylim(0, 15000) 97 | # as the function returns the axes- and 'mappable'-objects colorbar needs, adding a colorbar is easy 98 | cb = plt.colorbar(pm, ax=ax) 99 | ``` 100 | -------------------------------------------------------------------------------- /notebooks/fileio/backends/iris_backend.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: md:myst 4 | text_representation: 5 | extension: .md 6 | format_name: myst 7 | kernelspec: 8 | name: python3 9 | display_name: Python 3 10 | --- 11 | 12 | ```{include} ../../../_includes/license_block.md 13 | ``` 14 | # xarray IRIS backend 15 | 16 | In this example, we read IRIS (sigmet) data files using the xradar `iris` xarray backend. 17 | 18 | ```{code-cell} python 19 | import glob 20 | import gzip 21 | import io 22 | import wradlib as wrl 23 | import wradlib_data 24 | import warnings 25 | from IPython.display import display 26 | 27 | import matplotlib.pyplot as plt 28 | import numpy as np 29 | import xradar as xd 30 | import xarray as xr 31 | 32 | warnings.filterwarnings("ignore") 33 | 34 | ``` 35 | 36 | ## Load IRIS Volume Data 37 | 38 | ```{code-cell} python 39 | fpath = "sigmet/SUR210819000227.RAWKPJV" 40 | f = wradlib_data.DATASETS.fetch(fpath) 41 | vol = xd.io.open_iris_datatree(f, reindex_angle=False) 42 | ``` 43 | 44 | ## Inspect RadarVolume 45 | 46 | ```{code-cell} python 47 | display(vol) 48 | ``` 49 | 50 | ## Inspect root group 51 | 52 | The `sweep` dimension contains the number of scans in this radar volume. Further the dataset consists of variables (location coordinates, time_coverage) and attributes (Conventions, metadata). 53 | 54 | ```{code-cell} python 55 | vol.root 56 | ``` 57 | 58 | ## Inspect sweep group(s) 59 | 60 | The sweep-groups can be accessed via their respective keys. The dimensions consist of `range` and `time` with added coordinates `azimuth`, `elevation`, `range` and `time`. There will be variables like radar moments (DBZH etc.) and sweep-dependent metadata (like `fixed_angle`, `sweep_mode` etc.). 61 | 62 | ```{code-cell} python 63 | display(vol["sweep_0"]) 64 | ``` 65 | 66 | ## Georeferencing 67 | 68 | ```{code-cell} python 69 | swp = vol["sweep_0"].ds.copy() 70 | swp = swp.assign_coords(sweep_mode=swp.sweep_mode) 71 | swp = swp.wrl.georef.georeference() 72 | ``` 73 | 74 | ## Inspect radar moments 75 | 76 | The DataArrays can be accessed by key or by attribute. Each DataArray has dimensions and coordinates of it's parent dataset. 77 | 78 | ```{code-cell} python 79 | display(swp.DBZH) 80 | ``` 81 | 82 | ## Create simple plot 83 | 84 | Using xarray features a simple plot can be created like this. Note the `sortby('time')` method, which sorts the radials by time. 85 | 86 | For more details on plotting radar data see under [Visualization](../../visualisation/plotting). 87 | 88 | ```{code-cell} python 89 | swp.DBZH.sortby("time").plot(x="range", y="time", add_labels=False) 90 | ``` 91 | 92 | ```{code-cell} python 93 | fig = plt.figure(figsize=(5, 5)) 94 | pm = swp.DBZH.wrl.vis.plot(crs={"latmin": 3e3}, fig=fig) 95 | ``` 96 | 97 | ## Retrieve explicit group 98 | 99 | ```{code-cell} python 100 | swp_b = xr.open_dataset( 101 | f, engine="iris", group="sweep_0", backend_kwargs=dict(reindex_angle=False) 102 | ) 103 | display(swp_b) 104 | ``` 105 | -------------------------------------------------------------------------------- /notebooks/fileio/backends/rainbow_backend.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: md:myst 4 | text_representation: 5 | extension: .md 6 | format_name: myst 7 | kernelspec: 8 | name: python3 9 | display_name: Python 3 10 | --- 11 | 12 | ```{include} ../../../_includes/license_block.md 13 | ``` 14 | # xarray Rainbow5 backend 15 | 16 | In this example, we read Rainbow5 data files using the xradar `rainbow` xarray backend. 17 | 18 | ```{code-cell} python 19 | import glob 20 | import gzip 21 | import io 22 | import wradlib as wrl 23 | import wradlib_data 24 | import warnings 25 | from IPython.display import display 26 | 27 | import matplotlib.pyplot as plt 28 | import numpy as np 29 | import xradar as xd 30 | import xarray as xr 31 | 32 | warnings.filterwarnings("ignore") 33 | 34 | ``` 35 | 36 | ## Load Rainbow5 Volume Data 37 | 38 | ```{code-cell} python 39 | fpath = "rainbow/2013051000000600dBZ.vol" 40 | f = wradlib_data.DATASETS.fetch(fpath) 41 | vol = xd.io.open_rainbow_datatree(f, reindex_angle=False) 42 | ``` 43 | 44 | ## Inspect RadarVolume 45 | 46 | ```{code-cell} python 47 | display(vol) 48 | ``` 49 | 50 | ## Inspect root group 51 | 52 | The `sweep` dimension contains the number of scans in this radar volume. Further the dataset consists of variables (location coordinates, time_coverage) and attributes (Conventions, metadata). 53 | 54 | ```{code-cell} python 55 | vol.root 56 | ``` 57 | 58 | ## Inspect sweep group(s) 59 | 60 | The sweep-groups can be accessed via their respective keys. The dimensions consist of `range` and `time` with added coordinates `azimuth`, `elevation`, `range` and `time`. There will be variables like radar moments (DBZH etc.) and sweep-dependent metadata (like `fixed_angle`, `sweep_mode` etc.). 61 | 62 | ```{code-cell} python 63 | display(vol["sweep_0"]) 64 | ``` 65 | 66 | ## Georeferencing 67 | 68 | ```{code-cell} python 69 | swp = vol["sweep_0"].ds.copy() 70 | swp = swp.assign_coords(sweep_mode=swp.sweep_mode) 71 | swp = swp.wrl.georef.georeference() 72 | ``` 73 | 74 | ## Inspect radar moments 75 | 76 | The DataArrays can be accessed by key or by attribute. Each DataArray has dimensions and coordinates of its parent dataset. 77 | 78 | ```{code-cell} python 79 | display(swp.DBZH) 80 | ``` 81 | 82 | ## Create simple plot 83 | 84 | Using xarray features a simple plot can be created like this. Note the `sortby('time')` method, which sorts the radials by time. 85 | 86 | For more details on plotting radar data see under [Visualization](../../visualisation/plotting). 87 | 88 | ```{code-cell} python 89 | swp.DBZH.sortby("time").plot(x="range", y="time", add_labels=False) 90 | ``` 91 | 92 | ```{code-cell} python 93 | fig = plt.figure(figsize=(5, 5)) 94 | pm = swp.DBZH.wrl.vis.plot(crs={"latmin": 3e3}, fig=fig) 95 | ``` 96 | 97 | ## Retrieve explicit group 98 | 99 | ```{code-cell} python 100 | swp_b = xr.open_dataset( 101 | f, engine="rainbow", group="sweep_5", backend_kwargs=dict(reindex_angle=False) 102 | ) 103 | display(swp_b) 104 | ``` 105 | -------------------------------------------------------------------------------- /conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: UTF-8 -*- 3 | # Copyright (c) 2025, wradlib developers. 4 | # Distributed under the MIT License. See LICENSE-MIT for more info. 5 | 6 | import os 7 | import sys 8 | 9 | import pyproj 10 | import wradlib 11 | 12 | # set PROJ_LIB explicitly to overcome RTD issue 13 | os.environ["PROJ_LIB"] = pyproj.datadir.get_data_dir() 14 | os.environ["PROJ_NETWORK"] = "ON" 15 | 16 | sys.path.insert(0, os.path.abspath("..")) 17 | 18 | project = "wradlib notebooks" 19 | copyright = "2025, wradlib developers" 20 | author = "Kai Mühlbauer" 21 | 22 | wradlib_version = wradlib.__version__ 23 | html_title = " ".join([project, wradlib_version]) 24 | 25 | 26 | extensions = [ 27 | "sphinx.ext.autosectionlabel", 28 | "sphinx.ext.coverage", 29 | "sphinx.ext.extlinks", 30 | "sphinx.ext.intersphinx", 31 | "sphinx.ext.mathjax", 32 | "sphinx.ext.todo", 33 | "sphinx.ext.viewcode", 34 | # "sphinxcontrib.bibtex", 35 | "myst_nb", 36 | "sphinx_copybutton", 37 | "IPython.sphinxext.ipython_console_highlighting", 38 | ] 39 | 40 | myst_enable_extensions = [ 41 | "colon_fence", 42 | "deflist", 43 | "dollarmath", 44 | "amsmath", 45 | "html_image", 46 | "linkify", 47 | "substitution", 48 | "tasklist", 49 | "replacements", 50 | ] 51 | 52 | autosectionlabel_prefix_document = True 53 | 54 | myst_heading_anchors = 4 55 | 56 | source_suffix = { 57 | ".rst": "restructuredtext", 58 | ".ipynb": "notebook", 59 | } 60 | 61 | myst_nb = {"kernelspec": {"name": "python3", "display_name": "Python 3"}} 62 | 63 | myst_substitutions = { 64 | "wradlib": "$\\omega radlib$", 65 | "wradlib_version": wradlib_version, 66 | } 67 | 68 | intersphinx_mapping = { 69 | "python": ("https://docs.python.org/3/", None), 70 | "numpy": ("https://numpy.org/doc/stable/", None), 71 | "scipy": ("https://docs.scipy.org/doc/scipy/", None), 72 | "matplotlib": ("https://matplotlib.org/stable/", None), 73 | "sphinx": ("https://www.sphinx-doc.org/en/master/", None), 74 | "xarray": ("https://docs.xarray.dev/en/stable/", None), 75 | "xradar": ("https://docs.openradarscience.org/projects/xradar/en/stable/", None), 76 | "cartopy": ("https://cartopy.readthedocs.io/stable/", None), 77 | "gdal": ("https://gdal.org/en/stable/", None), 78 | "pyproj": ("https://pyproj4.github.io/pyproj/stable/", None), 79 | "wradlib": ("https://docs.wradlib.org/en/stable/", None), 80 | } 81 | 82 | 83 | master_doc = "index" 84 | 85 | html_theme = "sphinx_book_theme" 86 | 87 | html_theme_options = { 88 | "show_toc_level": 1, 89 | "collapse_navigation": True, 90 | "extra_footer": f"
Created with wradlib v{wradlib_version}
", 91 | } 92 | 93 | nb_execution_mode = "force" 94 | nb_execution_kernel_name = "python3" 95 | nb_execution_in_temp = False 96 | nb_execution_timeout = 300 97 | nb_execution_raise_on_error = False 98 | 99 | 100 | exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", "README.md", "**/.*"] 101 | -------------------------------------------------------------------------------- /notebooks/fileio/backends/gamic_backend.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: md:myst 4 | text_representation: 5 | extension: .md 6 | format_name: myst 7 | kernelspec: 8 | name: python3 9 | display_name: Python 3 10 | --- 11 | 12 | ```{include} ../../../_includes/license_block.md 13 | ``` 14 | # xarray GAMIC backend 15 | 16 | In this example, we read GAMIC (HDF5) data files using the xradar `gamic` backend. 17 | 18 | ```{code-cell} python 19 | import glob 20 | import wradlib as wrl 21 | import wradlib_data 22 | import warnings 23 | from IPython.display import display 24 | 25 | import matplotlib.pyplot as plt 26 | import numpy as np 27 | import xradar as xd 28 | import xarray as xr 29 | 30 | warnings.filterwarnings("ignore") 31 | 32 | ``` 33 | 34 | ## Load GAMIC HDF5 Volume Data 35 | 36 | ```{code-cell} python 37 | fpath = "hdf5/DWD-Vol-2_99999_20180601054047_00.h5" 38 | f = wradlib_data.DATASETS.fetch(fpath) 39 | vol = xd.io.open_gamic_datatree(f) 40 | ``` 41 | 42 | ## Inspect RadarVolume 43 | 44 | ```{code-cell} python 45 | display(vol) 46 | ``` 47 | 48 | ## Inspect root group 49 | 50 | The `sweep` dimension contains the number of scans in this radar volume. Further the dataset consists of variables (location coordinates, time_coverage) and attributes (Conventions, metadata). 51 | 52 | ```{code-cell} python 53 | vol.root 54 | ``` 55 | 56 | ## Inspect sweep group(s) 57 | 58 | The sweep-groups can be accessed via their respective keys. The dimensions consist of `range` and `time` with added coordinates `azimuth`, `elevation`, `range` and `time`. There will be variables like radar moments (DBZH etc.) and sweep-dependent metadata (like `fixed_angle`, `sweep_mode` etc.). 59 | 60 | ```{code-cell} python 61 | display(vol["sweep_0"]) 62 | ``` 63 | 64 | ## Georeferencing 65 | 66 | ```{code-cell} python 67 | swp = vol["sweep_0"].ds.copy() 68 | swp = swp.assign_coords(sweep_mode=swp.sweep_mode) 69 | ``` 70 | 71 | ```{code-cell} python 72 | swp = swp.wrl.georef.georeference() 73 | display(swp) 74 | ``` 75 | 76 | ## Inspect radar moments 77 | 78 | The DataArrays can be accessed by key or by attribute. Each DataArray has dimensions and coordinates of its parent dataset. There are attributes connected which are defined by ODIM_H5 standard. 79 | 80 | ```{code-cell} python 81 | display(swp.DBZH) 82 | ``` 83 | 84 | ## Create simple plot 85 | 86 | Using xarray features a simple plot can be created like this. Note the `sortby('time')` method, which sorts the radials by time. 87 | 88 | For more details on plotting radar data see under [Visualization](../../visualisation/plotting). 89 | 90 | ```{code-cell} python 91 | swp.DBZH.sortby("time").plot(x="range", y="time", add_labels=False) 92 | ``` 93 | 94 | ```{code-cell} python 95 | fig = plt.figure(figsize=(10, 10)) 96 | pm = swp.DBZH.wrl.vis.plot(crs={"latmin": 3e3}, fig=fig) 97 | ``` 98 | 99 | ## Retrieve explicit group 100 | 101 | 102 | ```{code-cell} python 103 | swp_b = xr.open_dataset( 104 | f, engine="gamic", group="sweep_9", backend_kwargs=dict(reindex_angle=False) 105 | ) 106 | display(swp_b) 107 | ``` 108 | -------------------------------------------------------------------------------- /notebooks/fileio/backends/cfradial2_backend.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: md:myst 4 | text_representation: 5 | extension: .md 6 | format_name: myst 7 | kernelspec: 8 | name: python3 9 | display_name: Python 3 10 | --- 11 | 12 | ```{include} ../../../_includes/license_block.md 13 | ``` 14 | # xarray CfRadial2 backend 15 | 16 | In this example, we read CfRadial2 data files using the xarray `cfradial2` backend. 17 | 18 | ```{code-cell} python 19 | import wradlib as wrl 20 | import wradlib_data 21 | import warnings 22 | from IPython.display import display 23 | 24 | import matplotlib.pyplot as plt 25 | import numpy as np 26 | import xradar as xd 27 | import xarray as xr 28 | 29 | warnings.filterwarnings("ignore") 30 | ``` 31 | 32 | ## Load CfRadial2 Volume Data 33 | 34 | ```{code-cell} python 35 | fpath = "netcdf/cfrad.20080604_002217_000_SPOL_v36_SUR_cfradial2.nc" 36 | f = wradlib_data.DATASETS.fetch(fpath) 37 | vol = xr.open_datatree(f) 38 | ``` 39 | 40 | ```{code-cell} python 41 | # fix: remove when available in xradar 42 | for k in vol.groups[1:]: 43 | vol[k].ds = ( 44 | vol[k] 45 | .ds.assign(sweep_fixed_angle=vol[k].ds.attrs["fixed_angle"]) 46 | .swap_dims(time="azimuth") 47 | .sortby("azimuth") 48 | ) 49 | ``` 50 | 51 | ## Inspect RadarVolume 52 | 53 | ```{code-cell} python 54 | display(vol) 55 | ``` 56 | 57 | ## Inspect root group 58 | 59 | The `sweep` dimension contains the number of scans in this radar volume. Further the dataset consists of variables (location coordinates, time_coverage) and attributes (Conventions, metadata). 60 | 61 | ```{code-cell} python 62 | vol.root 63 | ``` 64 | 65 | ## Inspect sweep group(s) 66 | 67 | The sweep-groups can be accessed via their respective keys. The dimensions consist of `range` and `time` with added coordinates `azimuth`, `elevation`, `range` and `time`. There will be variables like radar moments (DBZH etc.) and sweep-dependent metadata (like `fixed_angle`, `sweep_mode` etc.). 68 | 69 | ```{code-cell} python 70 | display(vol["sweep_0"]) 71 | ``` 72 | 73 | ## Georeferencing 74 | 75 | ```{code-cell} python 76 | swp = vol["sweep_0"].ds.copy() 77 | swp = swp.assign_coords(sweep_mode=swp.sweep_mode) 78 | swp = swp.wrl.georef.georeference() 79 | ``` 80 | 81 | ## Inspect radar moments 82 | 83 | The DataArrays can be accessed by key or by attribute. Each DataArray has dimensions and coordinates of its parent dataset. There are attributes connected which are defined by Cf/Radial standard. 84 | 85 | ```{code-cell} python 86 | display(swp.DBZ) 87 | ``` 88 | 89 | ## Create simple plot 90 | 91 | Using xarray features a simple plot can be created like this. Note the `sortby('time')` method, which sorts the radials by time. 92 | 93 | For more details on plotting radar data see under [Visualization](../../visualisation/plotting). 94 | 95 | ```{code-cell} python 96 | swp.DBZ.sortby("time").plot(x="range", y="time", add_labels=False) 97 | ``` 98 | 99 | ```{code-cell} python 100 | fig = plt.figure(figsize=(5, 5)) 101 | pm = swp.DBZ.wrl.vis.plot(crs={"latmin": 3e3}, fig=fig) 102 | ``` 103 | -------------------------------------------------------------------------------- /notebooks/python/numpyintro.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: md:myst 4 | text_representation: 5 | extension: .md 6 | format_name: myst 7 | kernelspec: 8 | name: python3 9 | display_name: Python 3 10 | --- 11 | 12 | ```{include} ../../_includes/license_block.md 13 | ``` 14 | # NumPy: manipulating numerical data 15 | 16 | *NumPy* is the key Python package for creating and manipulating (multi-dimensional) numerical arrays. *NumPy* arrays are also the most important data objects in $\omega radlib$. It has become a convention to import *NumPy* as follows: 17 | 18 | ```{code-cell} python 19 | import numpy as np 20 | ``` 21 | 22 | ## Creating and inspecting NumPy arrays 23 | 24 | The `ndarray`, a numerical array, is the most important data type in NumPy. 25 | 26 | ```{code-cell} python 27 | a = np.array([0, 1, 2, 3]) 28 | print(a) 29 | print(type(a)) 30 | ``` 31 | 32 | Inspect the `shape` (i.e. the number and size of the dimensions of an array). 33 | 34 | ```{code-cell} python 35 | print(a.shape) 36 | # This creates a 2-dimensional array 37 | a2 = np.array([[0, 1], [2, 3]]) 38 | print(a2.shape) 39 | ``` 40 | 41 | There are various ways to create arrays: from lists (as above), using convenience functions, or from file. 42 | 43 | ```{code-cell} python 44 | # From lists 45 | a = np.array([0, 1, 2, 3]) 46 | print("a looks like:\n%r\n" % a) 47 | 48 | # Convenience functions 49 | b = np.ones(shape=(2, 3)) 50 | print("b looks like:\n%r\nand has shape %r\n" % (b, b.shape)) 51 | 52 | c = np.zeros(shape=(2, 1)) 53 | print("c looks like:\n%r\nand has shape %r\n" % (c, c.shape)) 54 | 55 | d = np.arange(2, 10) 56 | print("d looks like:\n%r\nand has shape %r\n" % (d, d.shape)) 57 | 58 | e = np.linspace(0, 10, 5) 59 | print("e looks like:\n%r\nand has shape %r\n" % (e, e.shape)) 60 | ``` 61 | 62 | You can change the shape of an array without changing its size. 63 | 64 | ```{code-cell} python 65 | a = np.arange(10) 66 | b = np.reshape(a, (2, 5)) 67 | print("Array a has shape %r.\nArray b has shape %r" % (a.shape, b.shape)) 68 | ``` 69 | 70 | ## Indexing and slicing 71 | 72 | You can index an `ndarray` in the same way as a `list`: 73 | 74 | ```{code-cell} python 75 | a = np.arange(10) 76 | print(a) 77 | print(a[0], a[2], a[-1]) 78 | ``` 79 | 80 | Just follow your intuition for indexing multi-dimensional arrays: 81 | 82 | ```{code-cell} python 83 | a = np.diag(np.arange(3)) 84 | print(a, end="\n\n") 85 | 86 | print("Second row, second column: %r\n" % a[1, 1]) 87 | 88 | # Setting an array item 89 | a[2, 1] = 10 # third line, second column 90 | print(a, end="\n\n") 91 | 92 | # Acessing a full row 93 | print("Second row:\n%r" % a[1]) 94 | ``` 95 | 96 | Slicing is just a way to access multiple array items at once: 97 | 98 | ```{code-cell} python 99 | a = np.arange(10) 100 | print(a, end="\n\n") 101 | 102 | print("1st:", a[2:9]) 103 | print("2nd:", a[2:]) 104 | print("3rd:", a[:5]) 105 | print("4th:", a[2:9:3]) # [start:end:step] 106 | print("5th:", a[a > 5]) # using a mask 107 | ``` 108 | 109 | Get further info on NumPy arrays [here](https://scipy-lectures.org/intro/numpy/array_object.html#indexing-and-slicing)! 110 | -------------------------------------------------------------------------------- /notebooks/fileio/backends/odim_backend.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: md:myst 4 | text_representation: 5 | extension: .md 6 | format_name: myst 7 | kernelspec: 8 | name: python3 9 | display_name: Python 3 10 | --- 11 | 12 | ```{include} ../../../_includes/license_block.md 13 | ``` 14 | # xarray ODIM backend 15 | 16 | In this example, we read ODIM_H5 (HDF5) data files using the xradar `odim` backend. Throughout the notebook xarray accessors are used to access wradlib functionality. 17 | 18 | ```{code-cell} python 19 | import glob 20 | import os 21 | import wradlib as wrl 22 | import wradlib_data 23 | import warnings 24 | from IPython.display import display 25 | 26 | import matplotlib.pyplot as plt 27 | import numpy as np 28 | import xradar as xd 29 | import xarray as xr 30 | 31 | warnings.filterwarnings("ignore") 32 | 33 | ``` 34 | 35 | ## Load ODIM_H5 Volume Data 36 | 37 | ```{code-cell} python 38 | fpath = "hdf5/knmi_polar_volume.h5" 39 | f = wradlib_data.DATASETS.fetch(fpath) 40 | vol = xd.io.open_odim_datatree(f) 41 | ``` 42 | 43 | ## Inspect RadarVolume 44 | 45 | ```{code-cell} python 46 | display(vol) 47 | ``` 48 | 49 | ## Inspect root group 50 | 51 | The `sweep` dimension contains the number of scans in this radar volume. Further the dataset consists of variables (location coordinates, time_coverage) and attributes (Conventions, metadata). 52 | 53 | ```{code-cell} python 54 | vol.root 55 | ``` 56 | 57 | ## Inspect sweep group(s) 58 | 59 | The sweep-groups can be accessed via their respective keys. The dimensions consist of `range` and `time` with added coordinates `azimuth`, `elevation`, `range` and `time`. There will be variables like radar moments (DBZH etc.) and sweep-dependent metadata (like `fixed_angle`, `sweep_mode` etc.). 60 | 61 | ```{code-cell} python 62 | display(vol["sweep_0"]) 63 | ``` 64 | 65 | ## Georeferencing 66 | 67 | ```{code-cell} python 68 | swp = vol["sweep_0"].ds 69 | swp = swp.assign_coords(sweep_mode=swp.sweep_mode) 70 | swp = swp.wrl.georef.georeference() 71 | ``` 72 | 73 | ## Inspect radar moments 74 | 75 | The DataArrays can be accessed by key or by attribute. Each DataArray has dimensions and coordinates of its parent dataset. There are attributes connected which are defined by ODIM_H5 standard. 76 | 77 | ```{code-cell} python 78 | display(swp.DBZH) 79 | ``` 80 | 81 | ## Create simple plot 82 | 83 | Using xarray features a simple plot can be created like this. Note the `sortby('time')` method, which sorts the radials by time. 84 | 85 | For more details on plotting radar data see under [Visualization](../../visualisation/plotting). 86 | 87 | ```{code-cell} python 88 | swp.DBZH.sortby("time").plot(x="range", y="time", add_labels=False) 89 | ``` 90 | 91 | ```{code-cell} python 92 | fig = plt.figure(figsize=(10, 10)) 93 | pm = swp.DBZH.wrl.vis.plot(crs={"latmin": 33e3}, fig=fig) 94 | ``` 95 | 96 | ## Retrieve explicit group 97 | 98 | ```{code-cell} python 99 | swp_b = xr.open_dataset( 100 | f, engine="odim", group="sweep_13", backend_kwargs=dict(reindex_angle=False) 101 | ) 102 | display(swp_b) 103 | ``` 104 | 105 | ### Use `xr.open_mfdataset` to retrieve timeseries of explicit group 106 | 107 | ```{code-cell} python 108 | flist = ["hdf5/71_20181220_060628.pvol.h5", "hdf5/71_20181220_061228.pvol.h5"] 109 | flist = [wradlib_data.DATASETS.fetch(f) for f in flist] 110 | ts = xr.open_mfdataset( 111 | flist, engine="odim", concat_dim="volume_time", combine="nested", group="sweep_0" 112 | ) 113 | display(ts) 114 | ``` 115 | -------------------------------------------------------------------------------- /notebooks/fileio/backends/cfradial1_backend.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: md:myst 4 | text_representation: 5 | extension: .md 6 | format_name: myst 7 | kernelspec: 8 | name: python3 9 | display_name: Python 3 10 | --- 11 | 12 | ```{include} ../../../_includes/license_block.md 13 | ``` 14 | # xarray CfRadial1 backend 15 | 16 | In this example, we read CfRadial1 data files using the `xradar` `cfradial1` backend. 17 | 18 | Data is also exported to ODIM_H5 and CfRadial2. 19 | 20 | ```{code-cell} python 21 | import wradlib as wrl 22 | import wradlib_data 23 | import warnings 24 | from IPython.display import display 25 | 26 | import matplotlib.pyplot as plt 27 | import numpy as np 28 | import xradar as xd 29 | import xarray as xr 30 | 31 | warnings.filterwarnings("ignore") 32 | ``` 33 | 34 | ## Load CfRadial1 Volume Data 35 | 36 | We use the functionality provided now by [xradar](https://docs.openradarscience.org/projects/xradar/en/stable/) to read the CfRadial1 data into a DataTree. 37 | 38 | ```{code-cell} python 39 | fpath = "netcdf/cfrad.20080604_002217_000_SPOL_v36_SUR.nc" 40 | f = wradlib_data.DATASETS.fetch(fpath) 41 | vol = xd.io.open_cfradial1_datatree(f) 42 | ``` 43 | 44 | ## Inspect RadarVolume 45 | 46 | ```{code-cell} python 47 | display(vol) 48 | ``` 49 | 50 | ## Inspect root group 51 | 52 | The `sweep` dimension contains the number of scans in this radar volume. Further the dataset consists of variables (location coordinates, time_coverage) and attributes (Conventions, metadata). 53 | 54 | ```{code-cell} python 55 | vol.root 56 | ``` 57 | 58 | ## Inspect sweep group(s) 59 | 60 | The sweep-groups can be accessed via their respective keys. The dimensions consist of `range` and `time` with added coordinates `azimuth`, `elevation`, `range` and `time`. There will be variables like radar moments (DBZH etc.) and sweep-dependent metadata (like `fixed_angle`, `sweep_mode` etc.). 61 | 62 | ```{code-cell} python 63 | display(vol["sweep_0"]) 64 | ``` 65 | 66 | ## Georeferencing 67 | 68 | ``sweep_mode`` is assigned coordinate, as we need it available on the DataArray. 69 | 70 | ```{code-cell} python 71 | swp = vol["sweep_0"].ds 72 | swp = swp.assign_coords(sweep_mode=swp.sweep_mode) 73 | swp = swp.wrl.georef.georeference() 74 | display(swp) 75 | ``` 76 | 77 | ## Inspect radar moments 78 | 79 | The DataArrays can be accessed by key or by attribute. Each DataArray has dimensions and coordinates of its parent dataset. There are attributes connected which are defined by Cf/Radial standard. 80 | 81 | ```{code-cell} python 82 | display(swp.DBZ) 83 | ``` 84 | 85 | ## Create simple plot 86 | 87 | Using xarray features a simple plot can be created like this. Note the `sortby('time')` method, which sorts the radials by time. 88 | 89 | For more details on plotting radar data see under [Visualization](../../visualisation/plotting). 90 | 91 | ```{code-cell} python 92 | swp.DBZ.sortby("time").plot(x="range", y="time", add_labels=False) 93 | ``` 94 | 95 | ```{code-cell} python 96 | fig = plt.figure(figsize=(5, 5)) 97 | pm = swp.DBZ.wrl.vis.plot(crs={"latmin": 3e3}, fig=fig) 98 | ``` 99 | 100 | ## Use `xr.open_dataset` to retrieve explicit group 101 | 102 | ```{warning} 103 | Since $\omega radlib$ version 2.0 all xarray backend related functionality is imported from [xradar](https://github.com/openradar/xradar)-package. 104 | ``` 105 | 106 | ```{code-cell} python 107 | swp_b = xr.open_dataset( 108 | f, engine="cfradial1", group="sweep_1", backend_kwargs=dict(reindex_angle=False) 109 | ) 110 | display(swp_b) 111 | ``` 112 | -------------------------------------------------------------------------------- /notebooks/georeferencing/georef.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: md:myst 4 | text_representation: 5 | extension: .md 6 | format_name: myst 7 | format_version: 0.13 8 | jupytext_version: 1.18.1 9 | kernelspec: 10 | name: python3 11 | display_name: Python 3 12 | --- 13 | 14 | ```{include} ../../_includes/license_block.md 15 | ``` 16 | # Example for georeferencing a radar dataset 17 | 18 | ```{code-cell} python 19 | import warnings 20 | 21 | import matplotlib as mpl 22 | import matplotlib.pyplot as plt 23 | import wradlib as wrl 24 | import xradar as xd 25 | from matplotlib.patches import Rectangle 26 | 27 | warnings.filterwarnings("ignore") 28 | ``` 29 | 30 | **1st step:** Compute centroid coordinates and vertices of all radar bins in WGS84 (longitude and latitude). 31 | 32 | ```{code-cell} python 33 | swp = ( 34 | xd.model.create_sweep_dataset(rng=1000) 35 | .swap_dims(time="azimuth") 36 | .isel(range=slice(0, 100)) 37 | ) 38 | swp = swp.assign_coords(sweep_mode="azimuthal_surveillance") 39 | swp = swp.wrl.georef.georeference() 40 | swp 41 | ``` 42 | 43 | We can now generate the polygon vertices of the radar bins - with **each vertex in lon/lat coordinates**. 44 | 45 | ```{code-cell} python 46 | proj_wgs84 = wrl.georef.epsg_to_osr(4326) 47 | polygons = swp.wrl.georef.spherical_to_polyvert(crs=proj_wgs84, keep_attrs=True) 48 | polygons 49 | ``` 50 | 51 | ... or we can compute the corresponding centroids of all bins - - with **each centroid in lon/lat coordinates**. 52 | 53 | ```{code-cell} python 54 | centroids = swp.wrl.georef.spherical_to_centroids(crs=proj_wgs84, keep_attrs=True) 55 | centroids 56 | ``` 57 | 58 | In order to understand how vertices and centroids correspond, we can plot them together. 59 | 60 | ```{code-cell} python 61 | fig = plt.figure(figsize=(16, 16)) 62 | site = (polygons.longitude.values, polygons.latitude.values) 63 | 64 | aspect = (centroids[..., 0].max() - centroids[..., 0].min()) / ( 65 | centroids[..., 1].max() - centroids[..., 1].min() 66 | ) 67 | ax = fig.add_subplot(121, aspect=aspect) 68 | polycoll = mpl.collections.PolyCollection( 69 | polygons.isel(xy=slice(0, 2)), closed=True, facecolors="None", linewidth=0.1 70 | ) 71 | ax.add_collection(polycoll, autolim=True) 72 | # ax.plot(centroids[..., 0], centroids[..., 1], 'r+') 73 | plt.title("Zoom in\n(only possible for interactive plots).") 74 | ax.add_patch( 75 | Rectangle( 76 | (site[0] + 0.25, site[1] + 0.25), 77 | 0.2, 78 | 0.2 / aspect, 79 | edgecolor="red", 80 | facecolor="None", 81 | zorder=3, 82 | ) 83 | ) 84 | plt.xlim(centroids[..., 0].min(), centroids[..., 0].max()) 85 | plt.ylim(centroids[..., 1].min(), centroids[..., 1].max()) 86 | 87 | ax = fig.add_subplot(122, aspect=aspect) 88 | polycoll = mpl.collections.PolyCollection( 89 | polygons.isel(xy=slice(0, 2)), closed=True, facecolors="None" 90 | ) 91 | ax.add_collection(polycoll, autolim=True) 92 | ax.plot(centroids[..., 0], centroids[..., 1], "r+") 93 | plt.title("Zoom into red box of left plot") 94 | plt.xlim(site[0] + 0.25, site[0] + 0.25 + 0.2) 95 | plt.ylim(site[1] + 0.25, site[1] + 0.25 + 0.2 / aspect) 96 | ``` 97 | 98 | **2nd step:** Reproject the centroid coordinates to Gauss-Krueger Zone 3 (i.e. EPSG-Code 31467). 99 | 100 | ```{code-cell} python 101 | centroids_xyz = centroids.assign_coords(xyz=["x", "y", "z"]).to_dataset("xyz") 102 | centroids_xyz 103 | ``` 104 | 105 | ```{code-cell} python 106 | proj_gk3 = wrl.georef.epsg_to_osr(31467) 107 | centroids_xyz = centroids_xyz.wrl.georef.reproject(trg_crs=proj_gk3) 108 | centroids_xyz 109 | ``` 110 | -------------------------------------------------------------------------------- /notebooks/classify/clutter_cloud.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: md:myst 4 | text_representation: 5 | extension: .md 6 | format_name: myst 7 | format_version: 0.13 8 | jupytext_version: 1.18.1 9 | kernelspec: 10 | name: python3 11 | display_name: Python 3 12 | --- 13 | 14 | ```{include} ../../_includes/license_block.md 15 | ``` 16 | # Clutter detection by using space-born cloud images 17 | 18 | ```{code-cell} python 19 | import matplotlib.pyplot as plt 20 | import numpy as np 21 | import wradlib as wrl 22 | import wradlib_data 23 | import xarray as xr 24 | import xradar as xd 25 | from IPython.display import display 26 | from osgeo import osr 27 | ``` 28 | 29 | ## Read the radar data into DataTree 30 | 31 | ```{code-cell} python 32 | # read the radar volume scan 33 | filename = "hdf5/20130429043000.rad.bewid.pvol.dbzh.scan1.hdf" 34 | filename = wradlib_data.DATASETS.fetch(filename) 35 | pvol = xd.io.open_odim_datatree(filename) 36 | display(pvol) 37 | ``` 38 | 39 | ## Georeference sweeps 40 | 41 | ```{code-cell} python 42 | pvol1 = pvol.match("sweep*") 43 | display(pvol1) 44 | vol = [] 45 | for sweep in pvol1.values(): 46 | vol.append(sweep.to_dataset().pipe(wrl.georef.georeference)) 47 | vol = xr.concat(vol, dim="tilt") 48 | vol = vol.assign_coords(sweep_mode=vol.sweep_mode) 49 | display(vol) 50 | ``` 51 | 52 | ## Construct collocated satellite data 53 | 54 | ```{code-cell} python 55 | proj_radar = osr.SpatialReference() 56 | proj_radar.ImportFromWkt(vol.crs_wkt.attrs["crs_wkt"]) 57 | ``` 58 | 59 | ```{code-cell} python 60 | filename = "hdf5/SAFNWC_MSG3_CT___201304290415_BEL_________.h5" 61 | filename = wradlib_data.DATASETS.fetch(filename) 62 | ``` 63 | 64 | ```{code-cell} python 65 | sat_gdal = wrl.io.read_safnwc(filename) 66 | val_sat = wrl.georef.read_gdal_values(sat_gdal) 67 | coord_sat = wrl.georef.read_gdal_coordinates(sat_gdal) 68 | proj_sat = wrl.georef.read_gdal_projection(sat_gdal) 69 | coord_sat = wrl.georef.reproject(coord_sat, src_crs=proj_sat, trg_crs=proj_radar) 70 | ``` 71 | 72 | ```{code-cell} python 73 | coord_radar = np.stack((vol.x, vol.y), axis=-1) 74 | coord_sat[..., 0:2].reshape(-1, 2).shape, coord_radar[..., 0:2].reshape(-1, 2).shape 75 | ``` 76 | 77 | ```{code-cell} python 78 | interp = wrl.ipol.Nearest( 79 | coord_sat[..., 0:2].reshape(-1, 2), coord_radar[..., 0:2].reshape(-1, 2) 80 | ) 81 | ``` 82 | 83 | ```{code-cell} python 84 | val_sat = interp(val_sat.ravel()).reshape(coord_radar.shape[:-1]) 85 | ``` 86 | 87 | ## Estimate localisation errors 88 | 89 | ```{code-cell} python 90 | timelag = 9 * 60 91 | wind = 10 92 | error = np.absolute(timelag) * wind 93 | ``` 94 | 95 | ## Identify clutter based on collocated cloudtype 96 | 97 | ```{code-cell} python 98 | rscale = vol.range.diff("range").median().values 99 | clutter = wrl.classify.filter_cloudtype( 100 | vol.DBZH, val_sat, scale=rscale, smoothing=error 101 | ) 102 | ``` 103 | 104 | ## Assign to vol 105 | 106 | ```{code-cell} python 107 | vol = vol.assign(sat=(["tilt", "azimuth", "range"], val_sat)) 108 | vol = vol.assign(clutter=(["tilt", "azimuth", "range"], clutter.values)) 109 | display(vol) 110 | ``` 111 | 112 | ## Plot the results 113 | 114 | ```{code-cell} python 115 | fig = plt.figure(figsize=(16, 8)) 116 | 117 | tilt = 0 118 | 119 | ax = fig.add_subplot(131) 120 | pm = vol.DBZH[tilt].wrl.vis.plot(ax=ax) 121 | # plt.colorbar(pm, shrink=0.5) 122 | plt.title("Radar reflectivity") 123 | 124 | ax = fig.add_subplot(132) 125 | pm = vol.sat[tilt].wrl.vis.plot(ax=ax) 126 | # plt.colorbar(pm, shrink=0.5) 127 | plt.title("Satellite cloud classification") 128 | 129 | ax = fig.add_subplot(133) 130 | pm = vol.clutter[tilt].wrl.vis.plot(ax=ax) 131 | # plt.colorbar(pm, shrink=0.5) 132 | plt.title("Detected clutter") 133 | 134 | fig.tight_layout() 135 | ``` 136 | -------------------------------------------------------------------------------- /notebooks/classify/fuzzy_echo.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: md:myst 4 | text_representation: 5 | extension: .md 6 | format_name: myst 7 | format_version: 0.13 8 | jupytext_version: 1.18.1 9 | kernelspec: 10 | name: python3 11 | display_name: Python 3 12 | --- 13 | 14 | ```{include} ../../_includes/license_block.md 15 | ``` 16 | # Simple fuzzy echo classification from dual-pol moments 17 | 18 | ```{code-cell} python 19 | import warnings 20 | 21 | import matplotlib.pyplot as plt 22 | import numpy as np 23 | import wradlib 24 | import xarray as xr 25 | from IPython.display import display 26 | from wradlib.util import get_wradlib_data_file 27 | 28 | warnings.filterwarnings("ignore") 29 | ``` 30 | 31 | ## Setting the file paths 32 | 33 | ```{code-cell} python 34 | rhofile = get_wradlib_data_file("netcdf/TAG-20120801-140046-02-R.nc") 35 | phifile = get_wradlib_data_file("netcdf/TAG-20120801-140046-02-P.nc") 36 | reffile = get_wradlib_data_file("netcdf/TAG-20120801-140046-02-Z.nc") 37 | dopfile = get_wradlib_data_file("netcdf/TAG-20120801-140046-02-V.nc") 38 | zdrfile = get_wradlib_data_file("netcdf/TAG-20120801-140046-02-D.nc") 39 | mapfile = get_wradlib_data_file("hdf5/TAG_cmap_sweeps_0204050607.hdf5") 40 | ``` 41 | 42 | ## Read the data (radar moments and static clutter map) 43 | 44 | ```{code-cell} python 45 | # We need to organize our data as a dictionary 46 | dat = {} 47 | dat["rho"], attrs_rho = wradlib.io.read_edge_netcdf(rhofile) 48 | dat["phi"], attrs_phi = wradlib.io.read_edge_netcdf(phifile) 49 | dat["ref"], attrs_ref = wradlib.io.read_edge_netcdf(reffile) 50 | dat["dop"], attrs_dop = wradlib.io.read_edge_netcdf(dopfile) 51 | dat["zdr"], attrs_zdr = wradlib.io.read_edge_netcdf(zdrfile) 52 | dat["map"] = wradlib.io.from_hdf5(mapfile)[0][0] 53 | 54 | dat = {k: (["azimuth", "range"], v) for k, v in dat.items()} 55 | ``` 56 | 57 | ```{code-cell} python 58 | az, rng = dat["rho"][1].shape 59 | swp = xr.Dataset(dat, coords={"azimuth": np.arange(az), "range": np.arange(rng)}) 60 | swp = swp.assign_coords( 61 | dict( 62 | longitude=7, 63 | latitude=53, 64 | altitude=0, 65 | elevation=1, 66 | sweep_mode="azimuth_surveillance", 67 | ) 68 | ) 69 | swp = swp.wrl.georef.georeference() 70 | display(swp) 71 | ``` 72 | 73 | ## Identify non-meteorological echoes using fuzzy echo classification 74 | 75 | See [Crisologo et al. (2015)](https://link.springer.com/article/10.1007/s13143-014-0049-y) and [Vulpiani et al. (2012)](https://journals.ametsoc.org/doi/abs/10.1175/JAMC-D-10-05024.1) for details. 76 | 77 | ```{code-cell} python 78 | moments = dict(rho="rho", phi="phi", dop="dop", zdr="zdr", map="map") 79 | weights = {"zdr": 0.4, "rho": 0.4, "rho2": 0.4, "phi": 0.1, "dop": 0.1, "map": 0.5} 80 | prob, nanmask = swp.wrl.classify.classify_echo_fuzzy(moments, weights=weights) 81 | thresh = 0.5 82 | cmap = prob.where(prob < thresh, True, False) 83 | ``` 84 | 85 | ## View classfication results 86 | 87 | ```{code-cell} python 88 | fig = plt.figure(figsize=(12, 5)) 89 | 90 | # Horizontal reflectivity 91 | ax = plt.subplot(121, aspect="equal") 92 | pm = swp.ref.plot(x="x", y="y", ax=ax, cbar_kwargs=dict(label="dBZ")) 93 | ax = wradlib.vis.plot_ppi_crosshair(site=(0, 0, 0), ranges=[80, 160, 240]) 94 | plt.xlim(-240, 240) 95 | plt.ylim(-240, 240) 96 | plt.xlabel("# bins from radar") 97 | plt.ylabel("# bins from radar") 98 | 99 | # Echo classification 100 | ax = plt.subplot(122, aspect="equal") 101 | pm = cmap.where(~np.isnan(swp.ref)).plot( 102 | x="x", 103 | y="y", 104 | ax=ax, 105 | cmap="bwr", 106 | cbar_kwargs=dict(label="meterol. echo=0 - non-meteorol. echo=1"), 107 | ) 108 | ax = wradlib.vis.plot_ppi_crosshair(site=(0, 0, 0), ranges=[80, 160, 240]) 109 | plt.xlim(-240, 240) 110 | plt.ylim(-240, 240) 111 | plt.xlabel("# bins from radar") 112 | plt.ylabel("# bins from radar") 113 | ``` 114 | -------------------------------------------------------------------------------- /notebooks/workflow/recipe2.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: md:myst 4 | text_representation: 5 | extension: .md 6 | format_name: myst 7 | format_version: 0.13 8 | jupytext_version: 1.18.1 9 | kernelspec: 10 | name: python3 11 | display_name: Python 3 12 | --- 13 | 14 | ```{include} ../../_includes/license_block.md 15 | ``` 16 | # Recipe #2: Reading and visualizing an ODIM_H5 polar volume 17 | 18 | 19 | This recipe shows how extract the polar volume data from an ODIM_H5 hdf5 file (KNMI example file from OPERA), construct a 3-dimensional Cartesian volume and produce a diagnostic plot. The challenge for this file is that for each elevation angle, the scan strategy is different. 20 | 21 | ```{code-cell} python 22 | import datetime as dt 23 | import warnings 24 | 25 | import numpy as np 26 | import wradlib as wrl 27 | import wradlib_data 28 | import xarray as xr 29 | import xradar as xd 30 | from IPython.display import display 31 | from osgeo import osr 32 | 33 | warnings.filterwarnings("ignore") 34 | ``` 35 | 36 | ```{code-cell} python 37 | # read the data (sample file in WRADLIB_DATA) 38 | filename = wradlib_data.DATASETS.fetch("hdf5/knmi_polar_volume.h5") 39 | 40 | raw_dt = xd.io.open_odim_datatree(filename) 41 | display(raw_dt) 42 | ``` 43 | 44 | ```{code-cell} python 45 | proj = osr.SpatialReference() 46 | proj.ImportFromEPSG(32632) 47 | for key in list(raw_dt.children): 48 | if "sweep" in key: 49 | raw_dt[key].ds = raw_dt[key].ds.wrl.georef.georeference(crs=proj) 50 | ``` 51 | 52 | ```{code-cell} python 53 | swp_list = [] 54 | for key in list(raw_dt.children): 55 | if "sweep" in key: 56 | ds = raw_dt[key].ds 57 | xyz = ( 58 | xr.concat( 59 | [ 60 | ds.coords["x"].reset_coords(drop=True), 61 | ds.coords["y"].reset_coords(drop=True), 62 | ds.coords["z"].reset_coords(drop=True), 63 | ], 64 | "xyz", 65 | ) 66 | .stack(npoints=("azimuth", "range")) 67 | .transpose(..., "xyz") 68 | ) 69 | swp_list.append(xyz) 70 | xyz = xr.concat(swp_list, "npoints") 71 | ``` 72 | 73 | ```{code-cell} python 74 | swp_list[0] 75 | ``` 76 | 77 | ```{code-cell} python 78 | data_list = [] 79 | for key in list(raw_dt.children): 80 | if "sweep" in key: 81 | ds = raw_dt[key].ds 82 | data = ds.DBZH.stack(npoints=("azimuth", "range")) 83 | data_list.append(data) 84 | data = xr.concat(data_list, "npoints") 85 | ``` 86 | 87 | ```{code-cell} python 88 | # generate 3-D Cartesian target grid coordinates 89 | sitecoords = (raw_dt.longitude.values, raw_dt.latitude.values, raw_dt.altitude.values) 90 | maxrange = 200000.0 91 | minelev = 0.1 92 | maxelev = 25.0 93 | maxalt = 5000.0 94 | horiz_res = 2000.0 95 | vert_res = 250.0 96 | trgxyz, trgshape = wrl.vpr.make_3d_grid( 97 | sitecoords, proj, maxrange, maxalt, horiz_res, vert_res 98 | ) 99 | ``` 100 | 101 | ```{code-cell} python 102 | # interpolate to Cartesian 3-D volume grid 103 | tstart = dt.datetime.now() 104 | gridder = wrl.vpr.CAPPI( 105 | xyz.values, 106 | trgxyz, 107 | # gridshape=trgshape, 108 | maxrange=maxrange, 109 | minelev=minelev, 110 | maxelev=maxelev, 111 | ) 112 | vol = np.ma.masked_invalid(gridder(data.values).reshape(trgshape)) 113 | print("3-D interpolation took:", dt.datetime.now() - tstart) 114 | ``` 115 | 116 | ```{code-cell} python 117 | # diagnostic plot 118 | trgx = trgxyz[:, 0].reshape(trgshape)[0, 0, :] 119 | trgy = trgxyz[:, 1].reshape(trgshape)[0, :, 0] 120 | trgz = trgxyz[:, 2].reshape(trgshape)[:, 0, 0] 121 | wrl.vis.plot_max_plan_and_vert( 122 | trgx, 123 | trgy, 124 | trgz, 125 | vol, 126 | unit="dBZH", 127 | levels=range(-32, 60), 128 | cmap="turbo", 129 | ) 130 | ``` 131 | 132 | ```{note} 133 | In order to run the recipe code, you need to extract the sample data into a directory pointed to by environment variable ``WRADLIB_DATA``. 134 | ``` 135 | -------------------------------------------------------------------------------- /notebooks/python/mplintro.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: md:myst 4 | text_representation: 5 | extension: .md 6 | format_name: myst 7 | kernelspec: 8 | name: python3 9 | display_name: Python 3 10 | --- 11 | 12 | ```{include} ../../_includes/license_block.md 13 | ``` 14 | # Visualisation and plotting with Matplotlib 15 | 16 | Matplotlib is the key Python package for producing so called publication-ready plot. It provides the basis for $\omega radlib$'s entire {mod}`wradlib.vis`, and is typically used together with [NumPy](numpyintro) - which is the other major $\omega radlib$ dependency. 17 | 18 | ## Different ways to import matplotlib 19 | 20 | ### In a Python script 21 | 22 | ```{code-cell} python 23 | # This explicitely import the module into the namespace 24 | import matplotlib.pyplot as plt 25 | import numpy as np 26 | ``` 27 | 28 | ### In an IPython notebook 29 | 30 | ```python 31 | # This magic just sets up matplotlib's interactive mode 32 | %matplotlib 33 | ``` 34 | 35 | If you want to enable `inline plotting` 36 | 37 | (**mandatory if you use the** [Virtual Machine for Cross-Platform Weather Radar Science](https://openradarscience.org/vm-docs/)) 38 | 39 | ```python 40 | # This magic just sets up matplotlib's interactive mode 41 | %matplotlib inline 42 | ``` 43 | 44 | `%matplotlib inline` turns on "inline plotting", where plot graphics will appear in your notebook. This has important implications for interactivity: for inline plotting, commands in cells below the cell that outputs a plot will not affect the plot. For example, changing the color map is not possible from cells below the cell that creates a plot. However, for other backends, such as qt4, that open a separate window, cells below those that create the plot will change the plot - it is a live object in memory. If you are not using matplotlib in interactive mode at all, figures will only appear if you invoke `plt.show()`. 45 | 46 | If you do not want to use inline plotting, just use `%matplotlib` instead of `%matplotlib inline`. The Kernel has to be restarted for this change to become effective. 47 | 48 | If you want to magically import numpy and matplotlib 49 | ```python 50 | %pylab 51 | # or 52 | %pylab inline 53 | ``` 54 | 55 | In the following, we use a sightly different syntax for `matplotlib inline`. This is because the notebook needs to be convertable to a Python script where IPython magic does not work. Please don't let this confuse you... 56 | 57 | ```python 58 | # Instead of matplotlib inline 59 | import matplotlib.pyplot as plt 60 | 61 | try: 62 | get_ipython().run_line_magic("matplotlib inline") 63 | except: 64 | plt.ion() 65 | ``` 66 | 67 | ## Simple plots and decorations 68 | 69 | After these imports, inline plots should work right away, e.g. a simple line plot of the sinus function: 70 | 71 | ```{code-cell} python 72 | x = np.arange(0, 4 * np.pi, 0.1) 73 | y = np.sin(x) 74 | plt.plot(x, y) 75 | ``` 76 | 77 | ## More complex plots and fine control 78 | 79 | A matplotlib plot can be understood from an object-oriented perspective. Each plot consists of a figure object (like a canvas), an axes object (like a subplot or panel), and other objects such as a title, an axis, or a colorbar. 80 | 81 | ![alt text](https://matplotlib.org/_images/fig_map.png "Elements of plot") 82 | 83 | Accordingly, a plot can be developed by creating or modifying the different objects. For example, the size of the entire plot is controlled by the `Figure` object. Each `subplot` in that figure corresponds to an `axes` object. 84 | 85 | ```{code-cell} python 86 | # Create the figure object 87 | fig = plt.figure(figsize=(12, 8)) 88 | 89 | # Add first axes object (of a multi-panel plot with two rows and one column) 90 | ax = fig.add_subplot(211) 91 | plt.plot(x, np.sin(x)) 92 | plt.title("The Sinus Function") 93 | plt.xlabel("This is my x-axis label") 94 | 95 | # Add second axes object 96 | ax = fig.add_subplot(212) 97 | plt.plot(x, np.cos(x)) 98 | plt.title("The Cosinus Function") 99 | plt.xlabel("This is my x-axis label") 100 | 101 | # Make sure the elements of the plot are arranged properly 102 | plt.tight_layout() 103 | ``` 104 | -------------------------------------------------------------------------------- /notebooks/basics/wradlib_in_an_hour.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: md:myst 4 | text_representation: 5 | extension: .md 6 | format_name: myst 7 | kernelspec: 8 | name: python3 9 | display_name: Python 3 10 | --- 11 | 12 | ```{include} ../../_includes/license_block.md 13 | ``` 14 | # A one hour tour of wradlib 15 | 16 | ![caption](../files/cover_image.png) 17 | 18 | A guided tour of some $\omega radlib$ notebooks. 19 | 20 | ## Some background, first 21 | 22 | Development started in 2011...or more precisely: 23 | 24 | `October 26th, 2011` 25 | 26 | ### Key motivation 27 | 28 | `A community platform for collaborative development of algorithms` 29 | 30 | ## Your entry points 31 | 32 | ### Start out from [wradlib.org](https://wradlib.org) 33 | 34 | ### Documentation 35 | 36 | Check out the [online docs](https://docs.wradlib.org/) with tutorials and examples and a comprehensive [API reference](https://docs.wradlib.org/en/stable/reference.html). 37 | 38 | ### Openradar discourse 39 | 40 | Get help and connect with weather radar enthusiasts from all over the world at [openradar-discourse](https://openradar.discourse.group)! 41 | 42 | ## For developers 43 | 44 | See our [Contributing guide](https://docs.wradlib.org/en/stable/dev_guide.html). 45 | 46 | ## Installation 47 | 48 | See [Installation](https://docs.wradlib.org/en/stable/installation.html). 49 | 50 | ## To run our tutorials 51 | 52 | 1. Get [notebooks](https://github.com/wradlib/wradlib-notebooks) 53 | 2. Get [sample data](https://github.com/wradlib/wradlib-data) 54 | 3. Set environment variable `WRADLIB_DATA` 55 | 56 | See also: [Jupyter](https://docs.wradlib.org/en/stable/jupyter.html) 57 | 58 | ## Development paradigm 59 | 60 | ### Keep the magic to a minimum 61 | 62 | - transparent 63 | - flexible, but lower level 64 | 65 | ### Flat (or no) data model 66 | 67 | - pass data as numpy arrays, 68 | - and pass metadata as dictionaries. 69 | 70 | ### Labelled multi-dimensional arrays 71 | 72 | 73 | - combine data, coordinates and attributes in flexible structures with [xarray](https://docs.xarray.dev/en/stable/) and [xradar](https://docs.openradarscience.org/projects/xradar/en/stable/) 74 | - transparent multiprocessing using [dask](https://docs.dask.org/en/stable/) 75 | 76 | ## Import wradlib 77 | 78 | ```{code-cell} python 79 | import wradlib 80 | ``` 81 | 82 | ```{code-cell} python 83 | # check installed version 84 | print(wradlib.__version__) 85 | ``` 86 | 87 | In the next cell, type `wradlib.` and hit `Tab`. 88 | 89 | *Inpect the available modules and functions.* 90 | 91 | ```{code-cell} python 92 | ``` 93 | 94 | ## Reading and viewing data 95 | 96 | ### Read and quick-view 97 | Let's see how we can [read and quick-view a radar scan](../visualisation/plot_ppi). 98 | 99 | ### Zoo of file formats 100 | This notebook shows you how to [access various file formats](../fileio/fileio). 101 | 102 | ## Addressing observational errors and artefacts 103 | 104 | ### Attenuation 105 | 106 | In [this example](../attenuation/attenuation), we reconstruct path-integrated attenuation from single-pol data of the German Weather Service. 107 | 108 | ### Clutter detection 109 | 110 | wradlib provides several methods for clutter detection. [Here](../classify/fuzzy_echo), we look at an example that uses dual-pol moments and a simple fuzzy classification. 111 | 112 | ### Partial beam blockage 113 | 114 | In [this example](../beamblockage/beamblockage), wradlib attempts to quantify terrain-induced beam blockage from a DEM. 115 | 116 | ## Integration with other geodata 117 | 118 | ### Average precipitation over your river catchment 119 | 120 | In this example, we [compute zonal statistics](../zonalstats/zonalstats_quickstart) over polygons imported in a shapefile. 121 | 122 | ### Over and underlay of other geodata 123 | 124 | Often, you need to [present your radar data in context with other geodata](../visualisation/gis_overlay) (DEM, rivers, gauges, catchments, ...). 125 | 126 | ## Merging with other sensors 127 | 128 | ### Adjusting radar-based rainfall estimates by rain gauges 129 | 130 | In [this example](../multisensor/gauge_adjustment), we use synthetic radar and rain gauge observations and confront them with different adjustment techniques. 131 | -------------------------------------------------------------------------------- /notebooks/python/timeseries.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: md:myst 4 | text_representation: 5 | extension: .md 6 | format_name: myst 7 | format_version: 0.13 8 | jupytext_version: 1.18.1 9 | kernelspec: 10 | name: python3 11 | display_name: Python 3 12 | --- 13 | 14 | ```{include} ../../_includes/license_block.md 15 | ``` 16 | # Dealing with time series 17 | 18 | 19 | Dealing with radar data typically means implies dealing with time series (of radar records or rain gauge observations). This article gives a brief intro on how to deal with times series and datetimes in Python. 20 | 21 | 22 | ## The datetime module 23 | 24 | The datetime module provides a number of types to deal with dates, times, and time intervals. 25 | 26 | ```{code-cell} python 27 | import datetime as dt 28 | 29 | import matplotlib.pyplot as plt 30 | import numpy as np 31 | ``` 32 | 33 | There are different ways to create datetime objects. 34 | 35 | ```{code-cell} python 36 | # This is now (system time) 37 | now = dt.datetime.now() 38 | # Just using the date 39 | birth_van_rossum = dt.datetime(1956, 1, 31) 40 | # Providing both date and time 41 | first_wradlib_commit = dt.datetime(2011, 10, 26, 11, 54, 58) 42 | # Or initialising from a string 43 | erad_2016_begins = dt.datetime.strptime("2016-10-09 09:00:00", "%Y-%m-%d %H:%M:%S") 44 | ``` 45 | 46 | You can compute the difference between two datetime objects. 47 | 48 | ```{code-cell} python 49 | # Age of Guido van Rossum 50 | age_van_rossum = now - birth_van_rossum 51 | print("This is a %r object.\n" % type(age_van_rossum)) 52 | print("It looks like this: %r" % age_van_rossum) 53 | print( 54 | "and consists of\n\t%d days,\n\t%d seconds,\n\tand %d microseconds.\n" 55 | % (age_van_rossum.days, age_van_rossum.seconds, age_van_rossum.microseconds) 56 | ) 57 | # Age of wradlib 58 | age_wradlib = now - first_wradlib_commit 59 | # Time until (or since) beginning of ERAD 2016 OSS Short course 60 | from_to_erad2016 = now - erad_2016_begins 61 | 62 | print("Guido van Rossum is %d seconds old." % age_van_rossum.total_seconds()) 63 | print("wradlib's first commit was %d days ago." % age_wradlib.days) 64 | if from_to_erad2016.total_seconds() < 0: 65 | print( 66 | "The ERAD 2016 OSS Short course will start in %d days." % -from_to_erad2016.days 67 | ) 68 | else: 69 | print( 70 | "The ERAD 2016 OSS Short course took place %d days ago." % from_to_erad2016.days 71 | ) 72 | ``` 73 | 74 | Or you can create a `datetime.timedelta` object yourself 75 | and add/subtract a time interval from/to a `datetime` object. 76 | You can use any of these keywords: `days, seconds, microseconds, milliseconds, minutes, hours, weeks`, 77 | but `datetime.timedelta` will always represent the result in `days, seconds, microseconds`. 78 | 79 | ```{code-cell} python 80 | # This is an interval of two minutes 81 | print(dt.timedelta(minutes=1, seconds=60)) 82 | # And this is, too 83 | print(dt.timedelta(minutes=2)) 84 | now = dt.datetime.now() 85 | print("This is now: %s" % now) 86 | print("This is two minutes before: %s" % (now - dt.timedelta(minutes=2))) 87 | ``` 88 | 89 | The default string format of a `datetime` object corresponds to the [isoformat](https://en.wikipedia.org/wiki/ISO_8601). Using the `strftime` function, however, you can control string formatting yourself. The following example shows this feature together with other features we have learned before. The idea is to loop over time and generate corresponding string representations. We also store the `datetime` objects in a list. 90 | 91 | ```{code-cell} python 92 | start = dt.datetime(2016, 10, 9) 93 | end = dt.datetime(2016, 10, 14) 94 | interval = dt.timedelta(days=1) 95 | dtimes = [] 96 | print("These are the ERAD 2016 conference days (incl. short courses):") 97 | while start <= end: 98 | print(start.strftime("\t%A, %d. %B %Y")) 99 | dtimes.append(start) 100 | start += interval 101 | ``` 102 | 103 | [matplotlib](mplintro) generally understands `datetime` objects and tries to make sense of them in plots. 104 | 105 | ```{code-cell} python 106 | # Create some dummy data 107 | level = np.linspace(100, 0, len(dtimes)) 108 | 109 | # And add a time series plot 110 | fig = plt.figure(figsize=(10, 5)) 111 | ax = fig.add_subplot(111) 112 | plt.plot(dtimes, level, "bo", linestyle="dashed") 113 | plt.xlabel("Day of the conference", fontsize=15) 114 | plt.ylabel("Relative attentiveness (%)", fontsize=15) 115 | plt.title( 116 | "Development of participants' attentiveness during the conference", fontsize=15 117 | ) 118 | plt.tick_params(labelsize=12) 119 | ``` 120 | -------------------------------------------------------------------------------- /notebooks/basics/wradlib_get_rainfall.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: md:myst 4 | text_representation: 5 | extension: .md 6 | format_name: myst 7 | format_version: 0.13 8 | jupytext_version: 1.18.1 9 | kernelspec: 10 | name: python3 11 | display_name: Python 3 12 | --- 13 | 14 | ```{include} ../../_includes/license_block.md 15 | ``` 16 | # Converting Reflectivity to Rainfall 17 | 18 | 19 | Reflectivity (Z) and precipitation rate (R) can be related in form of a power law $Z=a \cdot R^b$. The parameters ``a`` and ``b`` depend on the type of precipitation (i.e. drop size distribution and water temperature). $\omega radlib$ provides a couple of functions that could be useful in this context. 20 | 21 | ```{code-cell} python 22 | import datetime as dt 23 | import warnings 24 | 25 | import matplotlib.pyplot as plt 26 | import numpy as np 27 | import wradlib as wrl 28 | import wradlib_data 29 | import xarray as xr 30 | 31 | warnings.filterwarnings("ignore") 32 | ``` 33 | 34 | The following example demonstrates the steps to convert from the common unit *dBZ* (decibel of the reflectivity factor *Z*) to rainfall intensity (in the unit of mm/h). This is an array of typical reflectivity values (**unit: dBZ**) 35 | 36 | ```{code-cell} python 37 | dBZ = np.array([20.0, 30.0, 40.0, 45.0, 50.0, 55.0]) 38 | print(dBZ) 39 | ``` 40 | 41 | Convert to reflectivity factor Z (**unit**: $mm^6/m^3$): 42 | 43 | ```{code-cell} python 44 | Z = wrl.trafo.idecibel(dBZ) 45 | print(Z) 46 | ``` 47 | 48 | Convert to rainfall intensity (**unit: mm/h**) using the Marshall-Palmer Z(R) parameters: 49 | 50 | ```{code-cell} python 51 | R = wrl.zr.z_to_r(Z, a=200.0, b=1.6) 52 | print(np.round(R, 2)) 53 | ``` 54 | 55 | Convert to rainfall depth (**unit: mm**) assuming a rainfall duration of five minutes (i.e. 300 seconds) 56 | 57 | ```{code-cell} python 58 | depth = wrl.trafo.r_to_depth(R, 300) 59 | print(np.round(depth, 2)) 60 | ``` 61 | 62 | ## An example with real radar data 63 | 64 | 65 | The following example is based on observations of the DWD C-band radar on mount Feldberg (SW-Germany). 66 | The figure shows a 15 minute accumulation of rainfall which was produced from three consecutive radar 67 | scans at 5 minute intervals between 17:30 and 17:45 on June 8, 2008. 68 | 69 | The radar data are read using {mod}`~wradlib.io.read_dx` function which returns an array of dBZ values and a metadata dictionary (see also {ref}`notebooks/fileio/legacy/read_dx:reading dx-data`). The conversion is carried out the same way as in the example above. The plot is produced using 70 | the function {func}`~wradlib.vis.plot`. 71 | 72 | ```{code-cell} python 73 | def read_data(dtimes): 74 | """Helper function to read raw data for a list of datetimes """ 75 | dalist = [] 76 | for i, dtime in enumerate(dtimes): 77 | f = wradlib_data.DATASETS.fetch( 78 | "dx/raa00-dx_10908-{0}-fbg---bin.gz".format(dtime) 79 | ) 80 | data, attrs = wrl.io.read_dx(f) 81 | radar_location = (8.003611, 47.873611, 1516.0) 82 | dtime = dt.datetime.strptime(dtime, "%y%m%d%H%M") 83 | dalist.append( 84 | wrl.georef.create_xarray_dataarray( 85 | data, 86 | r=np.arange(500, data.shape[1] * 1000 + 500, 1000), 87 | phi=attrs["azim"], 88 | theta=attrs["elev"], 89 | site=radar_location, 90 | sweep_mode="azimuth_surveillance", 91 | ).assign_coords(time=dtime) 92 | ) 93 | ds = xr.concat(dalist, "time") 94 | return ds.assign_coords(elevation=ds.elevation.median("time")) 95 | ``` 96 | 97 | Read data from radar Feldberg for three consecutive 5 minute intervals and compute the accumulated rainfall depth. 98 | 99 | ```{code-cell} python 100 | # Read 101 | dtimes = ["0806021735", "0806021740", "0806021745"] 102 | dBZ = read_data(dtimes) 103 | # Convert to rainfall intensity (mm/h) 104 | Z = dBZ.wrl.trafo.idecibel() 105 | R = Z.wrl.zr.z_to_r(a=200.0, b=1.6) 106 | # Convert to rainfall depth (mm) 107 | depth = R.wrl.trafo.r_to_depth(300) 108 | # Accumulate 15 minute rainfall depth over all three 5 minute intervals 109 | accum = depth.sum(dim="time") 110 | ``` 111 | 112 | Plot PPI of 15 minute rainfall depth 113 | 114 | ```{code-cell} python 115 | plt.figure(figsize=(10, 8)) 116 | da = accum.wrl.georef.georeference() 117 | cf = da.wrl.vis.plot(cmap="viridis") 118 | plt.xlabel("Easting from radar (m)") 119 | plt.ylabel("Northing from radar (m)") 120 | plt.title("Radar Feldberg\n15 min. rainfall depth, 2008-06-02 17:30-17:45 UTC") 121 | cb = plt.colorbar(cf, shrink=0.8) 122 | cb.set_label("mm") 123 | plt.grid(color="grey") 124 | ``` 125 | -------------------------------------------------------------------------------- /notebooks/fileio/legacy/read_odim.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: md:myst 4 | text_representation: 5 | extension: .md 6 | format_name: myst 7 | kernelspec: 8 | name: python3 9 | display_name: Python 3 10 | --- 11 | 12 | ```{include} ../../../_includes/license_block.md 13 | ``` 14 | # OPERA HDF5 (ODIM_H5) 15 | 16 | ```{code-cell} python 17 | import wradlib as wrl 18 | import wradlib_data 19 | import matplotlib.pyplot as plt 20 | import numpy as np 21 | import warnings 22 | 23 | 24 | warnings.filterwarnings("ignore") 25 | ``` 26 | 27 | [HDF5](https://www.hdfgroup.org/solutions/hdf5/) is a data model, library, and file format for storing and managing data. The [OPERA program](https://www.eumetnet.eu/activities/observations-programme/current-activities/opera/) developed a convention (or information model) on how to store and exchange radar data in hdf5 format. It is based on the work of [COST Action 717](https://www.cost.eu/actions/717/) and is used e.g. in real-time operations in the Nordic European countries. The OPERA Data and Information Model (ODIM) is documented [here under OPERA Publications](https://www.eumetnet.eu/activities/observations-programme/current-activities/opera/). Make use of these documents in order to understand the organization of OPERA hdf5 files! 28 | 29 | ```{warning} 30 | For radar data in ODIM_H5 format the [openradar community](https://openradarscience.org/) published [xradar](https://docs.openradarscience.org/projects/xradar/en/latest/) where xarray-based readers/writers are implemented. That particular code was ported from $\omega radlib$ to xradar. Please refer to xradar for enhancements for polar radar. 31 | 32 | From $\omega radlib$ version 1.19 `ODIM_H5` reading code is imported from [xradar](https://github.com/openradar/xradar)-package whenever and wherever necessary. 33 | 34 | Please read the more indepth notebook [odim_backend](../backends/odim_backend). 35 | ``` 36 | 37 | The hierarchical nature of HDF5 can be described as being similar to directories, files, and links on a hard-drive. Actual metadata are stored as so-called *attributes*, and these attributes are organized together in so-called *groups*. Binary data are stored as so-called *datasets*. As for ODIM_H5, the ``root`` (or top level) group contains three groups of metadata: these are called ``what`` (object, information model version, and date/time information), ``where`` (geographical information), and ``how`` (quality and optional/recommended metadata). For a very simple product, e.g. a CAPPI, the data is organized in a group called ``dataset1`` which contains another group called ``data1`` where the actual binary data are found in ``data``. In analogy with a file system on a hard-disk, the HDF5 file containing this simple product is organized like this: 38 | 39 | ``` 40 | / 41 | /what 42 | /where 43 | /how 44 | /dataset1 45 | /dataset1/data1 46 | /dataset1/data1/data 47 | ``` 48 | 49 | The philosophy behind the $\omega radlib$ interface to OPERA's data model is very straightforward: $\omega radlib$ simply translates the complete file structure to *one* dictionary and returns this dictionary to the user. Thus, the potential complexity of the stored data is kept and it is left to the user how to proceed with this data. The keys of the output dictionary are strings that correspond to the "directory trees" shown above. Each key ending with ``/data`` points to a Dataset (i.e. a numpy array of data). Each key ending with ``/what``, ``/where`` or ``/how`` points to another dictionary of metadata. The entire output can be obtained by: 50 | 51 | ```{code-cell} python 52 | fpath = "hdf5/knmi_polar_volume.h5" 53 | f = wradlib_data.DATASETS.fetch(fpath) 54 | fcontent = wrl.io.read_opera_hdf5(f) 55 | ``` 56 | 57 | The user should inspect the output obtained from his or her hdf5 file in order to see how access those items which should be further processed. In order to get a readable overview of the output dictionary, one can use the pretty printing module: 58 | 59 | ```{code-cell} python 60 | # which keywords can be used to access the content? 61 | print(fcontent.keys()) 62 | # print the entire content including values of data and metadata 63 | # (numpy arrays will not be entirely printed) 64 | print(fcontent["dataset1/data1/data"]) 65 | ``` 66 | 67 | Please note that in order to experiment with such datasets, you can download hdf5 sample data from the [OPERA](https://www.eumetnet.eu/activities/observations-programme/current-activities/opera/) or use the example data provided with the [wradlib-data](https://github.com/wradlib/wradlib-data/) repository. 68 | 69 | ```{code-cell} python 70 | fig = plt.figure(figsize=(10, 10)) 71 | da = wrl.georef.create_xarray_dataarray( 72 | fcontent["dataset1/data1/data"] 73 | ).wrl.georef.georeference() 74 | im = da.wrl.vis.plot(fig=fig, crs="cg") 75 | ``` -------------------------------------------------------------------------------- /notebooks/fileio/legacy/read_rainbow.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: md:myst 4 | text_representation: 5 | extension: .md 6 | format_name: myst 7 | kernelspec: 8 | name: python3 9 | display_name: Python 3 10 | --- 11 | 12 | ```{include} ../../../_includes/license_block.md 13 | ``` 14 | # Leonardo Rainbow 15 | 16 | ```{code-cell} python 17 | import wradlib as wrl 18 | import wradlib_data 19 | import matplotlib.pyplot as plt 20 | import numpy as np 21 | import warnings 22 | 23 | warnings.filterwarnings("ignore") 24 | ``` 25 | 26 | Rainbow refers to the commercial [RAINBOW®5 APPLICATION SOFTWARE](https://www.leonardogermany.com/en/products/rainbow-5) which exports data in an XML flavour, which due to binary data blobs violates XML standard. Leonardo/Gematronik provided python code for implementing this reader in $\omega radlib$, which is very much appreciated. 27 | 28 | The philosophy behind the $\omega radlib$ interface to Leonardos data model is very straightforward: $\omega radlib$ simply translates the complete xml file structure to *one* dictionary and returns this dictionary to the user. Thus, the potential complexity of the stored data is kept and it is left to the user how to proceed with this data. The keys of the output dictionary are strings that correspond to the "xml nodes" and "xml attributes". Each ``data`` key points to a Dataset (i.e. a numpy array of data). 29 | 30 | ```{warning} 31 | For radar data in Rainbow5 format the [openradar community](https://openradarscience.org/) published [xradar](https://docs.openradarscience.org/projects/xradar/en/latest/) where xarray-based readers/writers are implemented. That particular code was ported from $\omega radlib$ to xradar. Please refer to xradar for enhancements for polar radar. 32 | 33 | From $\omega radlib$ version 1.19 `Rainbow5` reading code is imported from [xradar](https://github.com/openradar/xradar)-package whenever and wherever necessary. 34 | 35 | Please read the more indepth notebook [rainbow_backend](../backends/rainbow_backend). 36 | ``` 37 | 38 | 39 | ## Load Rainbow file 40 | 41 | Such a file (typical ending: *.vol* or *.azi*) can be read by: 42 | 43 | ```{code-cell} python 44 | filename = wradlib_data.DATASETS.fetch("rainbow/2013070308340000dBuZ.azi") 45 | rbdict = wrl.io.read_rainbow(filename) 46 | ``` 47 | 48 | ## Check Contents 49 | 50 | ```{code-cell} python 51 | # which keyswords can be used to access the content? 52 | print(rbdict.keys()) 53 | # print the entire content including values of data and metadata 54 | # (numpy arrays will not be entirely printed) 55 | print(rbdict["volume"]["sensorinfo"]) 56 | ``` 57 | 58 | ## Get azimuthal data 59 | 60 | ```{code-cell} python 61 | azi = rbdict["volume"]["scan"]["slice"]["slicedata"]["rayinfo"]["data"] 62 | azidepth = float(rbdict["volume"]["scan"]["slice"]["slicedata"]["rayinfo"]["@depth"]) 63 | azirange = float(rbdict["volume"]["scan"]["slice"]["slicedata"]["rayinfo"]["@rays"]) 64 | azires = float(rbdict["volume"]["scan"]["slice"]["anglestep"]) 65 | azi = (azi * azirange / 2**azidepth) * azires 66 | ``` 67 | 68 | ## Create range array 69 | 70 | ```{code-cell} python 71 | stoprange = float(rbdict["volume"]["scan"]["slice"]["stoprange"]) 72 | rangestep = float(rbdict["volume"]["scan"]["slice"]["rangestep"]) 73 | r = np.arange(0, stoprange, rangestep) 74 | ``` 75 | 76 | ## Get reflectivity data 77 | 78 | ```{code-cell} python 79 | data = rbdict["volume"]["scan"]["slice"]["slicedata"]["rawdata"]["data"] 80 | datadepth = float(rbdict["volume"]["scan"]["slice"]["slicedata"]["rawdata"]["@depth"]) 81 | datamin = float(rbdict["volume"]["scan"]["slice"]["slicedata"]["rawdata"]["@min"]) 82 | datamax = float(rbdict["volume"]["scan"]["slice"]["slicedata"]["rawdata"]["@max"]) 83 | data = datamin + data * (datamax - datamin) / 2**datadepth 84 | ``` 85 | 86 | ## Get annotation data 87 | 88 | ```{code-cell} python 89 | unit = rbdict["volume"]["scan"]["slice"]["slicedata"]["rawdata"]["@type"] 90 | time = rbdict["volume"]["scan"]["slice"]["slicedata"]["@time"] 91 | date = rbdict["volume"]["scan"]["slice"]["slicedata"]["@date"] 92 | lon = rbdict["volume"]["sensorinfo"]["lon"] 93 | lat = rbdict["volume"]["sensorinfo"]["lat"] 94 | sensortype = rbdict["volume"]["sensorinfo"]["@type"] 95 | sensorname = rbdict["volume"]["sensorinfo"]["@name"] 96 | ``` 97 | 98 | ## Convert to DataArray 99 | 100 | ```{code-cell} python 101 | da = wrl.georef.create_xarray_dataarray(data, r=r, az=azi).wrl.georef.georeference() 102 | ``` 103 | 104 | ## Plot data with annotation 105 | 106 | ```{code-cell} python 107 | fig = plt.figure(figsize=(10, 8)) 108 | pm = da.wrl.vis.plot(fig=fig, crs="cg") 109 | 110 | cgax = plt.gca() 111 | title = "{0} {1} {2} {3}\n{4}E {5}N".format( 112 | sensortype, sensorname, date, time, lon, lat 113 | ) 114 | caax = cgax.parasites[0] 115 | paax = cgax.parasites[1] 116 | 117 | 118 | t = plt.title(title, fontsize=12) 119 | t.set_y(1.1) 120 | cbar = plt.colorbar(pm, ax=[cgax, caax, paax], pad=0.075) 121 | caax.set_xlabel("x_range [km]") 122 | caax.set_ylabel("y_range [km]") 123 | plt.text(1.0, 1.05, "azimuth", transform=caax.transAxes, va="bottom", ha="right") 124 | cbar.set_label("reflectivity [" + unit + "]") 125 | ``` 126 | -------------------------------------------------------------------------------- /notebooks/fileio/backends/furuno_backend.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: md:myst 4 | text_representation: 5 | extension: .md 6 | format_name: myst 7 | kernelspec: 8 | name: python3 9 | display_name: Python 3 10 | --- 11 | 12 | ```{include} ../../../_includes/license_block.md 13 | ``` 14 | # xradar furuno backend 15 | 16 | In this example, we read scn/scnx (furuno) data files using the xradar `furuno` xarray backend. 17 | 18 | ```{code-cell} python 19 | import glob 20 | import gzip 21 | import io 22 | import wradlib as wrl 23 | import wradlib_data 24 | import warnings 25 | from IPython.display import display 26 | 27 | import matplotlib.pyplot as plt 28 | import numpy as np 29 | import xradar as xd 30 | import xarray as xr 31 | 32 | warnings.filterwarnings("ignore") 33 | 34 | ``` 35 | 36 | ## Load furuno scn Data 37 | 38 | Data provided by University of Graz, Austria. 39 | 40 | ```{code-cell} python 41 | fpath = "furuno/0080_20210730_160000_01_02.scn.gz" 42 | f = wradlib_data.DATASETS.fetch(fpath) 43 | vol = xd.io.open_furuno_datatree(f, reindex_angle=False) 44 | ``` 45 | 46 | ### Inspect scn RadarVolume 47 | 48 | ```{code-cell} python 49 | display(vol) 50 | ``` 51 | 52 | ### Inspect scn root group 53 | 54 | The `sweep` dimension contains the number of scans in this radar volume. Further the dataset consists of variables (location coordinates, time_coverage) and attributes (Conventions, metadata). 55 | 56 | ```{code-cell} python 57 | vol.root 58 | ``` 59 | 60 | ### Inspect scn sweep group(s) 61 | 62 | The sweep-groups can be accessed via their respective keys. The dimensions consist of `range` and `time` with added coordinates `azimuth`, `elevation`, `range` and `time`. There will be variables like radar moments (DBZH etc.) and sweep-dependent metadata (like `fixed_angle`, `sweep_mode` etc.). 63 | 64 | ```{code-cell} python 65 | display(vol["sweep_0"]) 66 | ``` 67 | 68 | ### Georeferencing scn 69 | 70 | ```{code-cell} python 71 | swp = vol["sweep_0"].ds.copy() 72 | swp = swp.assign_coords(sweep_mode=swp.sweep_mode) 73 | swp = swp.wrl.georef.georeference() 74 | ``` 75 | 76 | ### Inspect scn radar moments 77 | 78 | The DataArrays can be accessed by key or by attribute. Each DataArray has dimensions and coordinates of its parent dataset. 79 | 80 | ```{code-cell} python 81 | display(swp.DBZH) 82 | ``` 83 | 84 | ### Create scn simple plot 85 | 86 | Using xarray features a simple plot can be created like this. Note the `sortby('time')` method, which sorts the radials by time. 87 | 88 | For more details on plotting radar data see under [Visualization](../../visualisation/plotting). 89 | 90 | ```{code-cell} python 91 | swp.DBZH.sortby("time").plot(x="range", y="time", add_labels=False) 92 | ``` 93 | 94 | ```{code-cell} python 95 | fig = plt.figure(figsize=(5, 5)) 96 | pm = swp.DBZH.wrl.vis.plot(crs={"latmin": 3e3}, fig=fig) 97 | ``` 98 | 99 | ## Load furuno scnx Data 100 | 101 | Data provided by GFZ German Research Centre for Geosciences. 102 | 103 | ```{code-cell} python 104 | fpath = "furuno/2006_20220324_000000_000.scnx.gz" 105 | f = wradlib_data.DATASETS.fetch(fpath) 106 | vol = xd.io.open_furuno_datatree(f, reindex_angle=False) 107 | ``` 108 | 109 | ### Inspect scnx RadarVolume 110 | 111 | ```{code-cell} python 112 | display(vol) 113 | ``` 114 | 115 | ### Inspect scnx root group 116 | 117 | The `sweep` dimension contains the number of scans in this radar volume. Further the dataset consists of variables (location coordinates, time_coverage) and attributes (Conventions, metadata). 118 | 119 | ```{code-cell} python 120 | vol.root 121 | ``` 122 | 123 | ### Inspect scnx sweep group(s) 124 | 125 | The sweep-groups can be accessed via their respective keys. The dimensions consist of `range` and `time` with added coordinates `azimuth`, `elevation`, `range` and `time`. There will be variables like radar moments (DBZH etc.) and sweep-dependent metadata (like `fixed_angle`, `sweep_mode` etc.). 126 | 127 | ```{code-cell} python 128 | display(vol["sweep_0"]) 129 | ``` 130 | 131 | ### Georeferencing scnx 132 | 133 | ```{code-cell} python 134 | swp = vol["sweep_0"].ds.copy() 135 | swp = swp.assign_coords(sweep_mode=swp.sweep_mode) 136 | swp = swp.wrl.georef.georeference() 137 | ``` 138 | 139 | ### Inspect scnx radar moments 140 | 141 | The DataArrays can be accessed by key or by attribute. Each DataArray has dimensions and coordinates of its parent dataset. 142 | 143 | ```{code-cell} python 144 | display(swp.DBZH) 145 | ``` 146 | 147 | ### Create scnx simple plot 148 | 149 | Currently the data dynamic range is left as read from the file. That way the difference between shortpulse and longpulse can be clearly seen. 150 | 151 | Using xarray features a simple plot can be created like this. Note the `sortby('time')` method, which sorts the radials by time. 152 | 153 | For more details on plotting radar data see under [Visualization](../../visualisation/plotting). 154 | 155 | ```{code-cell} python 156 | swp.DBZH.sortby("time").plot(x="range", y="time", add_labels=False) 157 | ``` 158 | 159 | ```{code-cell} python 160 | fig = plt.figure(figsize=(5, 5)) 161 | pm = swp.DBZH.wrl.vis.plot(crs={"latmin": 3e3}, fig=fig) 162 | ``` 163 | 164 | ## More Furuno loading mechanisms 165 | 166 | 167 | ### Use `xr.open_dataset` to retrieve explicit group 168 | 169 | ```{code-cell} python 170 | swp_b = xr.open_dataset(f, engine="furuno", backend_kwargs=dict(reindex_angle=False)) 171 | display(swp_b) 172 | ``` 173 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | env: 3 | micromamba_version: 2 4 | 5 | 6 | on: 7 | push: 8 | branches: [ main ] 9 | pull_request: 10 | branches: [ main ] 11 | 12 | workflow_dispatch: 13 | 14 | concurrency: 15 | group: ${{ github.workflow }}-${{ github.ref }} 16 | cancel-in-progress: true 17 | 18 | jobs: 19 | lint: 20 | name: Lint 21 | runs-on: ubuntu-latest 22 | steps: 23 | - name: Checkout repository 24 | uses: actions/checkout@v3 25 | 26 | - name: Set up Python 27 | uses: actions/setup-python@v4 28 | with: 29 | python-version: 3.11 30 | 31 | - name: Install dependencies 32 | run: | 33 | python -m pip install --upgrade pip 34 | pip install nbqa ruff black jupytext isort 35 | 36 | - name: Lint with Ruff 37 | run: nbqa ruff notebooks/**/*.md 38 | 39 | - name: Lint with Isort 40 | run: nbqa isort notebooks/**/*.md 41 | 42 | - name: Check formatting with Black 43 | run: nbqa black notebooks/**/*.md 44 | 45 | build_0: 46 | name: wradlib notebooks - linux 47 | runs-on: ubuntu-latest 48 | needs: [lint] 49 | defaults: 50 | run: 51 | shell: bash -l {0} 52 | env: 53 | WRADLIB_DATA: ./wradlib-data 54 | CONDA_ENV_FILE: environment.yml 55 | strategy: 56 | fail-fast: false 57 | matrix: 58 | python-version: ["3.13"] 59 | steps: 60 | - uses: actions/checkout@v5 61 | with: 62 | fetch-depth: 0 63 | - name: Install micromamba environment 64 | uses: mamba-org/setup-micromamba@v2 65 | with: 66 | environment-name: wradlib-notebooks 67 | environment-file: ${{env.CONDA_ENV_FILE}} 68 | cache-environment: true 69 | cache-environment-key: "${{runner.os}}-${{runner.arch}}-py${{env.PYTHON_VERSION}}-${{env.TODAY}}-${{hashFiles(env.CONDA_ENV_FILE)}}" 70 | create-args: >- 71 | python=${{matrix.python-version}} 72 | - name: Version Info 73 | run: | 74 | python -c "import wradlib; print(wradlib.version.version)" 75 | python -c "import wradlib; print(wradlib.show_versions())" 76 | - name: Render with pytest 77 | env: 78 | WRADLIB_EARTHDATA_BEARER_TOKEN: ${{ secrets.WRADLIB_EARTHDATA_BEARER_TOKEN }} 79 | run: | 80 | export WRADLIB_DATA=`realpath $WRADLIB_DATA` 81 | pytest -n auto --verbose --durations=15 --pyargs notebooks 82 | 83 | build_1: 84 | name: wradlib notebooks - macosx 85 | runs-on: macos-latest 86 | needs: [lint] 87 | defaults: 88 | run: 89 | shell: bash -l {0} 90 | env: 91 | WRADLIB_DATA: ./wradlib-data 92 | CONDA_ENV_FILE: environment.yml 93 | strategy: 94 | fail-fast: false 95 | matrix: 96 | python-version: ["3.13"] 97 | steps: 98 | - uses: actions/checkout@v5 99 | with: 100 | fetch-depth: 0 101 | - name: Install micromamba environment 102 | uses: mamba-org/setup-micromamba@v2 103 | with: 104 | environment-name: wradlib-notebooks 105 | environment-file: ${{env.CONDA_ENV_FILE}} 106 | cache-environment: true 107 | cache-environment-key: "${{runner.os}}-${{runner.arch}}-py${{env.PYTHON_VERSION}}-${{env.TODAY}}-${{hashFiles(env.CONDA_ENV_FILE)}}" 108 | create-args: >- 109 | python=${{matrix.python-version}} 110 | - name: Version Info 111 | run: | 112 | python -c "import wradlib; print(wradlib.version.version)" 113 | python -c "import wradlib; print(wradlib.show_versions())" 114 | - name: Render with pytest 115 | env: 116 | WRADLIB_EARTHDATA_BEARER_TOKEN: ${{ secrets.WRADLIB_EARTHDATA_BEARER_TOKEN }} 117 | run: | 118 | export WRADLIB_DATA=`python -c "import os, sys; print(os.path.realpath(sys.argv[1]))" $WRADLIB_DATA` 119 | pytest -n auto --verbose --durations=15 --pyargs notebooks 120 | 121 | build_2: 122 | name: wradlib notebooks - windows 123 | runs-on: windows-latest 124 | needs: [lint] 125 | defaults: 126 | run: 127 | shell: bash -l {0} 128 | env: 129 | WRADLIB_DATA: ./wradlib-data 130 | CONDA_ENV_FILE: environment.yml 131 | strategy: 132 | fail-fast: false 133 | matrix: 134 | python-version: ["3.13"] 135 | steps: 136 | - uses: actions/checkout@v5 137 | with: 138 | fetch-depth: 0 139 | - name: Install micromamba environment 140 | uses: mamba-org/setup-micromamba@v2 141 | with: 142 | environment-name: wradlib-notebooks 143 | environment-file: ${{env.CONDA_ENV_FILE}} 144 | cache-environment: true 145 | cache-environment-key: "${{runner.os}}-${{runner.arch}}-py${{env.PYTHON_VERSION}}-${{env.TODAY}}-${{hashFiles(env.CONDA_ENV_FILE)}}" 146 | create-args: >- 147 | python=${{matrix.python-version}} 148 | - name: Version Info 149 | run: | 150 | python -c "import wradlib; print(wradlib.version.version)" 151 | python -c "import wradlib; print(wradlib.show_versions())" 152 | - name: Test with pytest 153 | env: 154 | WRADLIB_EARTHDATA_BEARER_TOKEN: ${{ secrets.WRADLIB_EARTHDATA_BEARER_TOKEN }} 155 | run: | 156 | export WRADLIB_DATA=`python -c "import os, sys; print(os.path.realpath(sys.argv[1]))" $WRADLIB_DATA` 157 | pytest -n auto --verbose --durations=15 --pyargs notebooks 158 | -------------------------------------------------------------------------------- /notebooks/verification/verification.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: md:myst 4 | text_representation: 5 | extension: .md 6 | format_name: myst 7 | format_version: 0.13 8 | jupytext_version: 1.18.1 9 | kernelspec: 10 | name: python3 11 | display_name: Python 3 12 | --- 13 | 14 | ```{include} ../../_includes/license_block.md 15 | ``` 16 | # Routine verification measures for radar-based precipitation estimates 17 | 18 | ```{code-cell} python 19 | import warnings 20 | 21 | import matplotlib.pyplot as plt 22 | import numpy as np 23 | import wradlib as wrl 24 | import wradlib_data 25 | 26 | warnings.filterwarnings("ignore") 27 | ``` 28 | 29 | ## Extract bin values from a polar radar data set at rain gage locations 30 | 31 | 32 | ### Read polar data set 33 | 34 | ```{code-cell} python 35 | filename = wradlib_data.DATASETS.fetch("misc/polar_R_tur.gz") 36 | data = np.loadtxt(filename) 37 | ``` 38 | 39 | ### Define site coordinates (lon/lat) and polar coordinate system 40 | 41 | ```{code-cell} python 42 | r = np.arange(1, 129) 43 | az = np.linspace(0, 360, 361)[0:-1] 44 | site = (9.7839, 48.5861, 0) 45 | ``` 46 | 47 | ### Make up two rain gauge locations (say we want to work in Gaus Krueger zone 3) 48 | 49 | ```{code-cell} python 50 | # Define the projection via epsg-code 51 | crs = wrl.georef.epsg_to_osr(31467) 52 | # Coordinates of the rain gages in Gauss-Krueger 3 coordinates 53 | x, y = np.array([3557880, 3557890]), np.array([5383379, 5383375]) 54 | ``` 55 | 56 | ### Now extract the radar values at those bins that are closest to our rain gauges 57 | 58 | For this purppose, we use the PolarNeighbours class from wraldib's verify module. Here, we extract the 9 nearest bins... 59 | 60 | ```{code-cell} python 61 | polarneighbs = wrl.verify.PolarNeighbours(r, az, site, crs, x, y, nnear=9) 62 | radar_at_gages = polarneighbs.extract(data) 63 | print("Radar values at rain gauge #1: %r" % radar_at_gages[0].tolist()) 64 | print("Radar values at rain gauge #2: %r" % radar_at_gages[1].tolist()) 65 | ``` 66 | 67 | ### Retrieve the bin coordinates (all of them or those at the rain gauges) 68 | 69 | ```{code-cell} python 70 | binx, biny = polarneighbs.get_bincoords() 71 | binx_nn, biny_nn = polarneighbs.get_bincoords_at_points() 72 | ``` 73 | 74 | ### Plot the entire radar domain and zoom into the surrounding of the rain gauge locations 75 | 76 | ```{code-cell} python 77 | fig = plt.figure(figsize=(12, 12)) 78 | ax = fig.add_subplot(121) 79 | ax.plot(binx, biny, "r+") 80 | ax.plot(binx_nn, biny_nn, "b+", markersize=10) 81 | ax.plot(x, y, "bo") 82 | ax.axis("tight") 83 | ax.set_aspect("equal") 84 | plt.title("Full view") 85 | ax = fig.add_subplot(122) 86 | ax.plot(binx, biny, "r+") 87 | ax.plot(binx_nn, biny_nn, "b+", markersize=10) 88 | ax.plot(x, y, "bo") 89 | plt.xlim(binx_nn.min() - 5, binx_nn.max() + 5) 90 | plt.ylim(biny_nn.min() - 7, biny_nn.max() + 8) 91 | ax.set_aspect("equal") 92 | txt = plt.title("Zoom into rain gauge locations") 93 | plt.tight_layout() 94 | ``` 95 | 96 | ## Create a verification report 97 | 98 | In this example, we make up a true Kdp profile and verify our reconstructed Kdp. 99 | 100 | 101 | ### Create synthetic data and reconstruct KDP 102 | 103 | ```{code-cell} python 104 | # Synthetic truth 105 | dr = 0.5 106 | r = np.arange(0, 100, dr) 107 | kdp_true = np.sin(0.3 * r) 108 | kdp_true[kdp_true < 0] = 0.0 109 | phidp_true = np.cumsum(kdp_true) * 2 * dr 110 | # Synthetic observation of PhiDP with a random noise and gaps 111 | np.random.seed(1319622840) 112 | phidp_raw = phidp_true + np.random.uniform(-2, 2, len(phidp_true)) 113 | gaps = np.random.uniform(0, len(r), 20).astype("int") 114 | phidp_raw[gaps] = np.nan 115 | 116 | # linearly interpolate nan 117 | nans = np.isnan(phidp_raw) 118 | phidp_ipol = phidp_raw.copy() 119 | phidp_ipol[nans] = np.interp(r[nans], r[~nans], phidp_raw[~nans]) 120 | 121 | # Reconstruct PhiDP and KDP 122 | phidp_rawre, kdp_rawre = wrl.dp.phidp_kdp_vulpiani(phidp_raw, dr=dr) 123 | phidp_ipre, kdp_ipre = wrl.dp.phidp_kdp_vulpiani(phidp_ipol, dr=dr) 124 | 125 | # Plot results 126 | fig = plt.figure(figsize=(12, 8)) 127 | ax = fig.add_subplot(211) 128 | plt.plot(kdp_true, "g-", label="True KDP") 129 | plt.plot(kdp_rawre, "r-", label="Reconstructed Raw KDP") 130 | plt.plot(kdp_ipre, "b-", label="Reconstructed Ipol KDP") 131 | plt.grid() 132 | lg = plt.legend() 133 | 134 | ax = fig.add_subplot(212) 135 | plt.plot(r, phidp_true, "b--", label="True PhiDP") 136 | plt.plot(r, np.ma.masked_invalid(phidp_raw), "b-", label="Raw PhiDP") 137 | plt.plot(r, phidp_rawre, "r-", label="Reconstructed Raw PhiDP") 138 | plt.plot(r, phidp_ipre, "g-", label="Reconstructed Ipol PhiDP") 139 | plt.grid() 140 | lg = plt.legend(loc="lower right") 141 | txt = plt.xlabel("Range (km)") 142 | ``` 143 | 144 | ### Create the report 145 | 146 | ```{code-cell} python 147 | metrics_raw = wrl.verify.ErrorMetrics(kdp_true, kdp_rawre) 148 | metrics_raw.pprint() 149 | metrics_ip = wrl.verify.ErrorMetrics(kdp_true, kdp_ipre) 150 | metrics_ip.pprint() 151 | 152 | plt.subplots_adjust(wspace=0.5) 153 | ax = plt.subplot(121, aspect=1.0) 154 | ax.plot(metrics_raw.obs, metrics_raw.est, "bo") 155 | ax.plot([-1, 2], [-1, 2], "k--") 156 | plt.xlim(-0.3, 1.1) 157 | plt.ylim(-0.3, 1.1) 158 | xlabel = ax.set_xlabel("True KDP (deg/km)") 159 | ylabel = ax.set_ylabel("Reconstructed Raw KDP (deg/km)") 160 | ax = plt.subplot(122, aspect=1.0) 161 | ax.plot(metrics_ip.obs, metrics_ip.est, "bo") 162 | ax.plot([-1, 2], [-1, 2], "k--") 163 | plt.xlim(-0.3, 1.1) 164 | plt.ylim(-0.3, 1.1) 165 | xlabel = ax.set_xlabel("True KDP (deg/km)") 166 | ylabel = ax.set_ylabel("Reconstructed Ipol KDP (deg/km)") 167 | ``` 168 | -------------------------------------------------------------------------------- /notebooks/fileio/legacy/read_netcdf.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: md:myst 4 | text_representation: 5 | extension: .md 6 | format_name: myst 7 | kernelspec: 8 | name: python3 9 | display_name: Python 3 10 | --- 11 | 12 | ```{include} ../../../_includes/license_block.md 13 | ``` 14 | # Reading NetCDF 15 | 16 | This reader utilizes [netCDF4-python](https://unidata.github.io/netcdf4-python/). 17 | 18 | In this example, we read NetCDF files from different sources using a generic reader from $\omega radlib's$ io module. 19 | 20 | ```{code-cell} python 21 | import wradlib as wrl 22 | import wradlib_data 23 | from wradlib.io import read_generic_netcdf 24 | from wradlib.util import get_wradlib_data_file 25 | import os 26 | ``` 27 | 28 | ## NetCDF Format 29 | 30 | The NetCDF format also claims to be self-describing. However, as for all such formats, the developers of netCDF also admit that "[...] the mere use of netCDF is not sufficient to make data self-describing and meaningful to both humans and machines [...]" (see [here](https://www.unidata.ucar.edu/software/netcdf/conventions.html)). Different radar operators or data distributors will use different naming conventions and data hierarchies (i.e. "data models") that the reading program might need to know about. 31 | 32 | $\omega radlib$ provides two solutions to address this challenge. The first one ignores the concept of data models and just pulls all data and metadata from a NetCDF file ({func}`wradlib.io.read_generic_netcdf`). The second is designed for a specific data model used by the EDGE software ({func}`wradlib.io.read_edge_netcdf`). 33 | 34 | ```{warning} 35 | For radar data in CfRadial1 or CfRadial2 format the [openradar community](https://openradarscience.org/) published [xradar](https://docs.openradarscience.org/projects/xradar/en/latest/) where xarray-based readers/writers are implemented. That particular code was ported from $\omega radlib$ to xradar. Please refer to xradar for enhancements for polar radar. 36 | 37 | From $\omega radlib$ version 1.19 `CfRadial` reading code is imported from [xradar](https://github.com/openradar/xradar)-package whenever and wherever necessary. 38 | 39 | Please read the more indepth notebooks [cfradial1_backend](../backends/cfradial1_backend) and [cfradial2_backend](../backends/cfradial2_backend). 40 | ``` 41 | 42 | ## Generic NetCDF reader (includes CfRadial) 43 | 44 | $\omega radlib$ provides a function that will virtually read any NetCDF file irrespective of the data model: {func}`wradlib.io.read_generic_netcdf`. It is built upon Python's [netcdf4](https://unidata.github.io/netcdf4-python/) library. {func}`wradlib.io.read_generic_netcdf` will return only one object, a dictionary, that contains all the contents of the NetCDF file corresponding to the original file structure. This includes all the metadata, as well as the so called "dimensions" (describing the dimensions of the actual data arrays) and the "variables" which will contains the actual data. Users can use this dictionary at will in order to query data and metadata; however, they should make sure to consider the documentation of the corresponding data model. {func}`wradlib.io.read_generic_netcdf` has been shown to work with a lot of different data models, most notably **CfRadial** (see [here](https://ncar.github.io/CfRadial/) for details). A typical call to {func}`wradlib.io.read_generic_netcdf` would look like: 45 | 46 | ```{code-cell} python 47 | fpath = "netcdf/example_cfradial_ppi.nc" 48 | f = wradlib_data.DATASETS.fetch(fpath) 49 | outdict = wrl.io.read_generic_netcdf(f) 50 | for key in outdict.keys(): 51 | print(key) 52 | ``` 53 | 54 | # EDGE NetCDF 55 | 56 | EDGE is a commercial software for radar control and data analysis provided by the Enterprise Electronics Corporation. It allows for netCDF data export. The resulting files can be read by {func}`wradlib.io.read_generic_netcdf`, but $\omega radlib$ also provides a specific function, {func}`wradlib.io.read_edge_netcdf` to return metadata and data as separate objects: 57 | 58 | ```{code-cell} python 59 | fpath = "netcdf/edge_netcdf.nc" 60 | f = wradlib_data.DATASETS.fetch(fpath) 61 | data, metadata = wrl.io.read_edge_netcdf(f) 62 | print(data.shape) 63 | print(metadata.keys()) 64 | ``` 65 | 66 | ```{code-cell} python 67 | # A little helper function for repeated tasks 68 | def read_and_overview(filename): 69 | """Read NetCDF using read_generic_netcdf and print upper level dictionary keys""" 70 | test = read_generic_netcdf(filename) 71 | print("\nPrint keys for file %s" % os.path.basename(filename)) 72 | for key in test.keys(): 73 | print("\t%s" % key) 74 | ``` 75 | 76 | ## CfRadial example from S-Pol research radar TIMREX campaign 77 | 78 | See also: https://www.eol.ucar.edu/field_projects/timrex 79 | 80 | ```{code-cell} python 81 | fpath = "netcdf/cfrad.20080604_002217_000_SPOL_v36_SUR.nc" 82 | filename = wradlib_data.DATASETS.fetch(fpath) 83 | read_and_overview(filename) 84 | ``` 85 | 86 | ## Example PPI from Py-ART repository 87 | 88 | See also: https://github.com/ARM-DOE/pyart/ 89 | 90 | ```{code-cell} python 91 | fpath = "netcdf/example_cfradial_ppi.nc" 92 | filename = wradlib_data.DATASETS.fetch(fpath) 93 | read_and_overview(filename) 94 | ``` 95 | 96 | ## Example RHI from Py-ART repository 97 | See also: https://github.com/ARM-DOE/pyart/ 98 | 99 | ```{code-cell} python 100 | fpath = "netcdf/example_cfradial_rhi.nc" 101 | filename = wradlib_data.DATASETS.fetch(fpath) 102 | read_and_overview(filename) 103 | ``` 104 | 105 | ## Example EDGE NetCDF export format 106 | 107 | ```{code-cell} python 108 | fpath = "netcdf/edge_netcdf.nc" 109 | filename = wradlib_data.DATASETS.fetch(fpath) 110 | read_and_overview(filename) 111 | ``` 112 | -------------------------------------------------------------------------------- /notebooks/classify/hmcp_gpm.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: md:myst 4 | text_representation: 5 | extension: .md 6 | format_name: myst 7 | format_version: 0.13 8 | jupytext_version: 1.18.1 9 | kernelspec: 10 | name: python3 11 | display_name: Python 3 12 | --- 13 | 14 | ```{include} ../../_includes/license_block.md 15 | ``` 16 | # Hydrometeor partitioning ratio retrievals for GPM 17 | 18 | In this notebook, GPM Dual Frequency Radar (DPR) measurements are used to derive Hydrometeor Partitioning Ratios (HPR) according to Pejcic et al 2025 (in review). This requires the measured Ku-band reflectivity, the dual-frequency ratios (Ku-band - Ka-band) and the DPR temperature and rain type information. The HPRs for the different hydrometeor classes are then presented. 19 | 20 | ```{code-cell} python 21 | import warnings 22 | 23 | import matplotlib.pyplot as plt 24 | import numpy as np 25 | import wradlib as wrl 26 | import wradlib_data 27 | import xarray as xr 28 | from dask.diagnostics import ProgressBar 29 | from IPython.display import display 30 | 31 | warnings.filterwarnings("ignore") 32 | ``` 33 | 34 | ## Read dual-frequency satellite observations (GPM) 35 | 36 | ```{code-cell} python 37 | path_gpm = wradlib_data.DATASETS.fetch( 38 | "gpm/2A-CS-VP-24.GPM.DPR.V9-20211125.20180625-S050710-E051028.024557.V07A.HDF5" 39 | ) 40 | # Read GPM data 41 | sr_data = wrl.io.open_gpm_dataset(path_gpm, group="FS").chunk(nray=1) 42 | sr_data = sr_data.set_coords(["Longitude", "Latitude"]) 43 | sr_data = xr.decode_cf(sr_data) 44 | ``` 45 | 46 | ## Plot GPM overpass 47 | 48 | ```{code-cell} python 49 | plt.figure(figsize=(5, 4)) 50 | sr_data.zFactorFinalNearSurface.isel(nfreq=0).plot( 51 | x="Longitude", 52 | y="Latitude", 53 | vmin=0, 54 | vmax=40, 55 | cmap="turbo", 56 | ) 57 | ``` 58 | 59 | ## Assign coordinates 60 | 61 | ```{code-cell} python 62 | sr_data = sr_data.set_coords("height") 63 | sr_data = sr_data.assign_coords(nbin=sr_data.nbin.data) 64 | sr_data = sr_data.assign_coords(nscan=sr_data.nscan.data) 65 | sr_data = sr_data.assign_coords(nray=sr_data.nray.data) 66 | ``` 67 | 68 | ## Plot overview along track 69 | 70 | ```{code-cell} python 71 | zlvl = np.arange(10, 57.5, 2.5) 72 | zlvl2 = np.arange(10, 57.5, 5) 73 | dpr_lvl = np.array([-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 25, 30]) 74 | 75 | ff = 10 76 | lw = 2.5 77 | x1, x2 = -101, -98.5 78 | y1, y2 = 0, 15000 79 | 80 | fig, axs = plt.subplots(1, 3, figsize=(20, 5)) # , sharey='row', sharex='col' 81 | 82 | # Ku_measured 83 | KU = sr_data.zFactorMeasured.sel(nfreq=0, nray=19) 84 | plot = KU.plot( 85 | ax=axs[0], 86 | x="Longitude", 87 | y="height", 88 | cmap="HomeyerRainbow", 89 | levels=zlvl, 90 | cbar_kwargs={"extend": "neither", "label": "", "pad": 0.01, "ticks": zlvl2}, 91 | xlim=(x1, x2), 92 | ylim=(y1, y2), 93 | ) 94 | 95 | colorbar = plot.colorbar 96 | colorbar.ax.tick_params(labelsize=ff) 97 | 98 | # Ka_measured 99 | KA = sr_data.zFactorMeasured.sel(nfreq=1, nray=19) 100 | plot = KA.plot( 101 | ax=axs[1], 102 | x="Longitude", 103 | y="height", 104 | cmap="HomeyerRainbow", 105 | levels=zlvl, 106 | cbar_kwargs={"extend": "neither", "label": "", "pad": 0.01, "ticks": zlvl2}, 107 | xlim=(x1, x2), 108 | ylim=(y1, y2), 109 | ) 110 | 111 | colorbar = plot.colorbar 112 | colorbar.ax.tick_params(labelsize=ff) 113 | 114 | 115 | # DFR_measured 116 | DFR = sr_data.zFactorMeasured.sel(nfreq=0, nray=19) - sr_data.zFactorMeasured.sel( 117 | nfreq=1, nray=19 118 | ) 119 | 120 | plot = DFR.plot( 121 | ax=axs[2], 122 | x="Longitude", 123 | y="height", 124 | cmap="HomeyerRainbow", 125 | levels=dpr_lvl, 126 | cbar_kwargs={"extend": "neither", "label": "", "pad": 0.01, "ticks": dpr_lvl}, 127 | xlim=(x1, x2), 128 | ylim=(y1, y2), 129 | ) 130 | 131 | colorbar = plot.colorbar 132 | colorbar.ax.tick_params(labelsize=ff) 133 | 134 | T = [r"$Z_m^{K_u}$ in dBZ", r"$Z_m^{K_a}$ in dBZ", r"$DFR_m^{K_u-K_a}$ in dB"] 135 | for i in range(len(T)): 136 | axs[i].set_title("", fontsize=ff) 137 | axs[i].set_title(T[i], fontsize=ff, loc="right") 138 | axs[i].set_ylabel("Height in m", fontsize=ff) 139 | axs[i].set_xlabel("Longitude in deg", fontsize=ff) 140 | axs[i].grid(ls=":", zorder=-100) 141 | axs[i].tick_params(axis="both", labelsize=ff) 142 | ``` 143 | 144 | ```{code-cell} python 145 | # centroids and covariances 146 | cdp_file = wradlib_data.DATASETS.fetch("misc/hmcp_centroids_df.nc") 147 | with xr.open_dataset(cdp_file) as cdp: 148 | cdp 149 | cdp 150 | ``` 151 | 152 | ```{code-cell} python 153 | # weights 154 | weights_file = wradlib_data.DATASETS.fetch("misc/hmcp_weights.nc") 155 | with xr.open_dataset(weights_file) as cw: 156 | display(cw) 157 | pass 158 | ``` 159 | 160 | ```{code-cell} python 161 | with ProgressBar(): 162 | obs = wrl.classify.create_gpm_observations(sr_data) 163 | obs 164 | ``` 165 | 166 | ```{code-cell} python 167 | %%time 168 | with ProgressBar(): 169 | hmpr = wrl.classify.calculate_hmpr(obs, cw.weights, cdp) # .compute() 170 | hmpr 171 | ``` 172 | 173 | ```{code-cell} python 174 | hmpr = hmpr.chunk(hmc=1, nray=1) 175 | hmpr 176 | ``` 177 | 178 | ```{code-cell} python 179 | with ProgressBar(): 180 | hmpr_sel = hmpr.sel(nray=19) * 100 181 | hmpr_sel = hmpr_sel.compute() 182 | hmpr_sel 183 | ``` 184 | 185 | ```{code-cell} python 186 | hpr_bins = [0, 1, 2.5, 5, 10, 15, 20, 30, 40, 50, 60, 70, 80, 90, 95, 100] 187 | x1, x2 = -101, -98.5 188 | y1, y2 = 0, 15000 189 | with ProgressBar(): 190 | hmpr_sel.plot( 191 | col="hmc", 192 | col_wrap=3, 193 | x="Longitude", 194 | y="height", 195 | cmap="HomeyerRainbow", 196 | levels=hpr_bins, 197 | xlim=(x1, x2), 198 | ylim=(y1, y2), 199 | cbar_kwargs={"ticks": hpr_bins}, 200 | ) 201 | ``` 202 | -------------------------------------------------------------------------------- /notebooks/fileio/gis/raster_data.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: md:myst 4 | text_representation: 5 | extension: .md 6 | format_name: myst 7 | kernelspec: 8 | name: python3 9 | display_name: Python 3 10 | --- 11 | 12 | ```{include} ../../../_includes/license_block.md 13 | ``` 14 | # Export a dataset in GIS-compatible format 15 | 16 | In this notebook, we demonstrate how to export a gridded dataset in GeoTIFF and ESRI ASCII format. This will be exemplified using RADOLAN data from the German Weather Service. 17 | 18 | You have two options for output: 19 | 20 | - `rioxarray.to_raster` 21 | - builtin GDAL functionality 22 | 23 | ```{code-cell} python 24 | import matplotlib.pyplot as plt 25 | import os 26 | import wradlib as wrl 27 | import wradlib_data 28 | import xarray as xr 29 | import numpy as np 30 | import warnings 31 | from pyproj.crs import CRS 32 | from IPython.display import display 33 | 34 | warnings.filterwarnings("ignore") 35 | 36 | ``` 37 | 38 | ## Step 1: Read the original data 39 | 40 | ```{code-cell} python 41 | # We will export this RADOLAN dataset to a GIS compatible format 42 | wdir = wradlib_data.DATASETS.abspath / "radolan/grid/" 43 | # create output-folder if not exists 44 | wdir.mkdir(parents=True, exist_ok=True) 45 | 46 | filename = "radolan/misc/raa01-sf_10000-1408102050-dwd---bin.gz" 47 | filename = wradlib_data.DATASETS.fetch(filename) 48 | ds = xr.open_dataset(filename, engine="radolan") 49 | display(ds) 50 | ``` 51 | 52 | ```{code-cell} python 53 | # This is the RADOLAN projection 54 | proj_osr = wrl.georef.create_osr("dwd-radolan") 55 | crs = CRS.from_wkt(proj_osr.ExportToWkt(["FORMAT=WKT2_2018"])) 56 | print(proj_osr) 57 | ``` 58 | 59 | ## Step 2a (output with rioxarray) 60 | 61 | 62 | drop encoding 63 | 64 | ```{code-cell} python 65 | ds.SF.encoding = {} 66 | ``` 67 | 68 | ```{code-cell} python 69 | ds = ds.rio.write_crs(crs) 70 | ds.SF.rio.to_raster(wdir / "geotiff_rio.tif", driver="GTiff") 71 | ``` 72 | 73 | ```{code-cell} python 74 | ds.SF.rio.to_raster( 75 | wdir / "aaigrid_rio.asc", 76 | driver="AAIGrid", 77 | profile_kwargs=dict(options=["DECIMAL_PRECISION=2"]), 78 | ) 79 | ``` 80 | 81 | ## Step 2b: (output with GDAL) 82 | 83 | ### Get the projected coordinates of the RADOLAN grid 84 | 85 | ```{code-cell} python 86 | # Get projected RADOLAN coordinates for corner definition 87 | xy_raw = wrl.georef.get_radolan_grid(900, 900) 88 | xy_raw.shape 89 | ``` 90 | 91 | ### Check Origin and Row/Column Order 92 | 93 | We know, that {func}`wradlib.io.read_radolan_composite` returns a 2D-array (rows, cols) with the origin in the lower left corner. Same applies to {func}`wradlib.georef.get_radolan_grid`. For the next step, we need to flip the data and the coords up-down. The coordinate corner points also need to be adjusted from lower left corner to upper right corner. 94 | 95 | ```{code-cell} python 96 | data, xy = wrl.georef.set_raster_origin(ds.SF.values, xy_raw, "upper") 97 | print(data.shape) 98 | ``` 99 | 100 | ### Export as GeoTIFF 101 | 102 | For RADOLAN grids, this projection will probably not be recognized by 103 | ESRI ArcGIS. 104 | 105 | ```{code-cell} python 106 | # create 3 bands 107 | data = np.stack((data, data + 100, data + 1000), axis=0) 108 | print(data.shape) 109 | gds = wrl.georef.create_raster_dataset(data, xy, crs=proj_osr) 110 | wrl.io.write_raster_dataset(wdir / "geotiff.tif", gds, driver="GTiff") 111 | ``` 112 | 113 | ### Export as ESRI ASCII file (aka Arc/Info ASCII Grid) 114 | 115 | ```{code-cell} python 116 | # Export to Arc/Info ASCII Grid format (aka ESRI grid) 117 | # It should be possible to import this to most conventional 118 | # GIS software. 119 | # only use first band 120 | proj_esri = proj_osr.Clone() 121 | proj_esri.MorphToESRI() 122 | ds = wrl.georef.create_raster_dataset(data[0], xy, crs=proj_esri) 123 | wrl.io.write_raster_dataset( 124 | wdir / "aaigrid.asc", ds, driver="AAIGrid", options=["DECIMAL_PRECISION=2"] 125 | ) 126 | ``` 127 | 128 | ## Step 3a: Read with xarray/rioxarray 129 | 130 | ```{code-cell} python 131 | fig = plt.figure(figsize=(15, 6)) 132 | ax1 = fig.add_subplot(121) 133 | with xr.open_dataset(wdir / "geotiff.tif") as ds1: 134 | display(ds1) 135 | ds1.sel(band=1).band_data.plot(ax=ax1) 136 | ax2 = fig.add_subplot(122) 137 | with xr.open_dataset(wdir / "geotiff_rio.tif") as ds2: 138 | display(ds2) 139 | ds2.sel(band=1).band_data.plot(ax=ax2) 140 | ``` 141 | 142 | ```{code-cell} python 143 | fig = plt.figure(figsize=(15, 6)) 144 | ax1 = fig.add_subplot(121) 145 | with xr.open_dataset(wdir / "aaigrid.asc") as ds1: 146 | display(ds1) 147 | ds1.sel(band=1).band_data.plot(ax=ax1) 148 | ax2 = fig.add_subplot(122) 149 | with xr.open_dataset(wdir / "aaigrid_rio.asc") as ds2: 150 | display(ds2) 151 | ds2.sel(band=1).band_data.plot(ax=ax2) 152 | ``` 153 | 154 | ## Step 3b: Read with GDAL 155 | 156 | ```{code-cell} python 157 | fig = plt.figure(figsize=(15, 6)) 158 | ax1 = fig.add_subplot(121) 159 | ds1 = wrl.io.open_raster(wdir / "geotiff.tif") 160 | data1, xy1, proj1 = wrl.georef.extract_raster_dataset(ds1, nodata=-9999.0) 161 | ax1.pcolormesh(xy1[..., 0], xy1[..., 1], data1[0]) 162 | 163 | ax2 = fig.add_subplot(122) 164 | ds2 = wrl.io.open_raster(wdir / "geotiff_rio.tif") 165 | data2, xy2, proj2 = wrl.georef.extract_raster_dataset(ds2, nodata=-9999.0) 166 | ax2.pcolormesh(xy2[..., 0], xy2[..., 1], data2) 167 | ``` 168 | 169 | ```{code-cell} python 170 | fig = plt.figure(figsize=(15, 6)) 171 | ax1 = fig.add_subplot(121) 172 | ds1 = wrl.io.open_raster(wdir / "aaigrid.asc") 173 | data1, xy1, proj1 = wrl.georef.extract_raster_dataset(ds1, nodata=-9999.0) 174 | ax1.pcolormesh(xy1[..., 0], xy1[..., 1], data1) 175 | 176 | ax2 = fig.add_subplot(122) 177 | ds2 = wrl.io.open_raster(wdir / "aaigrid_rio.asc") 178 | data2, xy2, proj2 = wrl.georef.extract_raster_dataset(ds2, nodata=-9999.0) 179 | ax2.pcolormesh(xy2[..., 0], xy2[..., 1], data2) 180 | ``` 181 | -------------------------------------------------------------------------------- /notebooks/fileio/legacy/read_dx.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: md:myst 4 | text_representation: 5 | extension: .md 6 | format_name: myst 7 | kernelspec: 8 | name: python3 9 | display_name: Python 3 10 | --- 11 | 12 | ```{include} ../../../_includes/license_block.md 13 | ``` 14 | # Handling DX Radar Data (German Weather Service) 15 | 16 | This tutorial helps you to read and plot the raw polar radar data provided by German Weather Service (DWD). 17 | 18 | ```{code-cell} python 19 | import wradlib as wrl 20 | import wradlib_data 21 | import matplotlib.pyplot as plt 22 | import warnings 23 | import numpy as np 24 | warnings.filterwarnings("ignore") 25 | 26 | ``` 27 | 28 | ## Reading DX-Data 29 | 30 | The German weather service provides polar radar data in the so called ``DX`` format. 31 | These have to be unpacked and transferred into an array of 360 (azimuthal resolution of 1 degree) by 128 (range resolution of 1 km). 32 | 33 | The naming convention for DX data is: 34 |
raa00-dx_<location-id>-<YYMMDDHHMM>-<location-abreviation>---bin
or
raa00-dx_<location-id>-<YYYYMMDDHHMM>-<location-abreviation>---bin
35 | For example: ``raa00-dx_10908-200608281420-fbg---bin`` raw data from radar station Feldberg (fbg, 10908) from 2006-08-28 14:20:00. 36 | 37 | Each DX file also contains additional information like the elevation angle for each beam. Note, however, that the format is not "self-describing". 38 | 39 | 40 | ### Raw data for one time step 41 | 42 | 43 | Suppose we want to read a radar-scan for a defined time step. You need to make sure that the data file is given with the correct path to the file. The read_dx function returns two variables: the reflectivity array, and a dictionary of metadata attributes. 44 | 45 | ```{code-cell} python 46 | filename = wradlib_data.DATASETS.fetch("dx/raa00-dx_10908-200608281420-fbg---bin.gz") 47 | one_scan, attributes = wrl.io.read_dx(filename) 48 | print(one_scan.shape) 49 | print(attributes.keys()) 50 | print(attributes["radarid"]) 51 | ``` 52 | 53 | ### Raw data for multiple time steps 54 | 55 | 56 | To read multiple scans into one array, you should create an empty array with the shape of the desired dimensions. 57 | In this example, the dataset contains 2 timesteps of 360 by 128 values. Note that we simply catch the metadata dictionary 58 | in a dummy variable: 59 | 60 | ```{code-cell} python 61 | import numpy as np 62 | 63 | two_scans = np.empty((2, 360, 128)) 64 | metadata = [[], []] 65 | filename = wradlib_data.DATASETS.fetch("dx/raa00-dx_10908-0806021740-fbg---bin.gz") 66 | two_scans[0], metadata[0] = wrl.io.read_dx(filename) 67 | filename = wradlib_data.DATASETS.fetch("dx/raa00-dx_10908-0806021745-fbg---bin.gz") 68 | two_scans[1], metadata[1] = wrl.io.read_dx(filename) 69 | print(two_scans.shape) 70 | ``` 71 | 72 | ## Visualizing dBZ values 73 | 74 | 75 | Now we want to create a quick diagnostic PPI plot of reflectivity in a polar coordinate system: 76 | 77 | ```{code-cell} python 78 | plt.figure(figsize=(10, 8)) 79 | da = wrl.georef.create_xarray_dataarray(one_scan).wrl.georef.georeference() 80 | pm = da.wrl.vis.plot() 81 | # add a colorbar with label 82 | cbar = plt.colorbar(pm, shrink=0.75) 83 | cbar.set_label("Reflectivity (dBZ)") 84 | ``` 85 | 86 | This is a stratiform event. Apparently, the radar system has already masked the foothills of the Alps as clutter. 87 | The spike in the south-western sector is caused by a broadcasting tower nearby the radar antenna. 88 | 89 | Another case shows a convective situation: 90 | 91 | ```{code-cell} python 92 | plt.figure(figsize=(10, 8)) 93 | da = wrl.georef.create_xarray_dataarray(two_scans[0]).wrl.georef.georeference() 94 | pm = da.wrl.vis.plot() 95 | cbar = plt.colorbar(pm, shrink=0.75) 96 | cbar.set_label("Reflectivity (dBZ)") 97 | ``` 98 | 99 | You can also modify or decorate the image further, e.g. add a cross-hair, a title, use a different colormap, or zoom in: 100 | 101 | ```{code-cell} python 102 | plt.figure(figsize=(10, 8)) 103 | # Plot PPI, 104 | pm = da.wrl.vis.plot() 105 | # add crosshair, 106 | ax = wrl.vis.plot_ppi_crosshair((0, 0, 0), ranges=[40, 80, 128]) 107 | # add colorbar, 108 | cbar = plt.colorbar(pm, shrink=0.9) 109 | cbar.set_label("Reflectivity (dBZ)") 110 | # add title, 111 | plt.title("Reflectivity at {0}\nDWD radar Feldberg".format(metadata[0]["datetime"])) 112 | # and zoom in. 113 | plt.xlim((-128, 128)) 114 | plt.ylim((-128, 128)) 115 | ``` 116 | 117 | In addition, you might want to tweak the colorscale to allow for better comparison of different images: 118 | 119 | ```{code-cell} python 120 | fig = plt.figure(figsize=(12, 10)) 121 | # Add first subplot (stratiform) 122 | ax = plt.subplot(121, aspect="equal") 123 | # Plot PPI, 124 | da = wrl.georef.create_xarray_dataarray(one_scan).wrl.georef.georeference() 125 | pm = da.wrl.vis.plot(ax=ax, vmin=20, vmax=60) 126 | # add crosshair, 127 | ax = wrl.vis.plot_ppi_crosshair((0, 0, 0), ranges=[40, 80, 128]) 128 | # add colorbar, 129 | cbar = plt.colorbar(pm, shrink=0.5) 130 | cbar.set_label("Reflectivity (dBZ)") 131 | # add title, 132 | plt.title("Reflectivity at {0}\nDWD radar Feldberg".format(metadata[0]["datetime"])) 133 | # and zoom in. 134 | plt.xlim((-128, 128)) 135 | plt.ylim((-128, 128)) 136 | # Add second subplot (convective) 137 | ax = plt.subplot(122, aspect="equal") 138 | # Plot PPI, 139 | da = wrl.georef.create_xarray_dataarray(two_scans[0]).wrl.georef.georeference() 140 | pm = da.wrl.vis.plot(ax=ax, vmin=20, vmax=60) 141 | # add crosshair, 142 | ax = wrl.vis.plot_ppi_crosshair((0, 0, 0), ranges=[40, 80, 128]) 143 | # add colorbar, 144 | cbar = plt.colorbar(pm, shrink=0.5) 145 | cbar.set_label("Reflectivity (dBZ)") 146 | # add title, 147 | plt.title("Reflectivity at {0}\nDWD radar Feldberg".format(attributes["datetime"])) 148 | # and zoom in. 149 | plt.xlim((-128, 128)) 150 | plt.ylim((-128, 128)) 151 | ``` 152 | 153 | The radar data was kindly provided by the German Weather Service. 154 | -------------------------------------------------------------------------------- /notebooks/visualisation/plot_ppi.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: md:myst 4 | text_representation: 5 | extension: .md 6 | format_name: myst 7 | format_version: 0.13 8 | jupytext_version: 1.18.1 9 | kernelspec: 10 | name: python3 11 | display_name: Python 3 12 | --- 13 | 14 | ```{include} ../../_includes/license_block.md 15 | ``` 16 | # Quick-view a sweep in polar or cartesian reference systems 17 | 18 | ```{code-cell} python 19 | import warnings 20 | 21 | import cartopy.crs as ccrs 22 | import cmweather # noqa: F401 23 | import matplotlib.pyplot as plt 24 | import numpy as np 25 | import wradlib as wrl 26 | import wradlib_data 27 | 28 | warnings.filterwarnings("ignore") 29 | ``` 30 | 31 | ## Read a polar data set from the German Weather Service 32 | 33 | ```{code-cell} python 34 | filename = wradlib_data.DATASETS.fetch("dx/raa00-dx_10908-0806021735-fbg---bin.gz") 35 | print(filename) 36 | ``` 37 | 38 | ```{code-cell} python 39 | img, meta = wrl.io.read_dx(filename) 40 | ``` 41 | 42 | Inspect the data set a little 43 | 44 | ```{code-cell} python 45 | print("Shape of polar array: %r\n" % (img.shape,)) 46 | print("Some meta data of the DX file:") 47 | print("\tdatetime: %r" % (meta["datetime"],)) 48 | print("\tRadar ID: %s" % (meta["radarid"],)) 49 | ``` 50 | 51 | ## The simplest plot 52 | 53 | ```{code-cell} python 54 | r = np.arange(img.shape[1], dtype=float) 55 | r += (r[1] - r[0]) / 2.0 56 | r *= 1000 57 | az = np.arange(img.shape[0], dtype=float) 58 | az += (az[1] - az[0]) / 2.0 59 | img = wrl.georef.create_xarray_dataarray(img, r=r, phi=az, site=(10, 45, 0)) 60 | img 61 | ``` 62 | 63 | ```{code-cell} python 64 | img = img.wrl.georef.georeference() 65 | pm = img.wrl.vis.plot() 66 | txt = plt.title("Simple PPI") 67 | ``` 68 | 69 | ## Plot in curvelinear grid 70 | 71 | ```{code-cell} python 72 | fig = plt.figure(figsize=(16, 8)) 73 | pm = img.wrl.vis.plot(crs="cg", ax=121, fig=fig) 74 | ``` 75 | 76 | ## Plot in projected coordinate system 77 | 78 | ```{code-cell} python 79 | epsg = wrl.georef.epsg_to_osr(31466) 80 | img = img.wrl.georef.georeference(crs=epsg) 81 | 82 | fig = plt.figure(figsize=(10, 10)) 83 | pm = img.wrl.vis.plot(ax=111, fig=fig) 84 | txt = plt.title("Simple PPI in Gauss-Krüger zone 2") 85 | ``` 86 | 87 | ## Plot in WGS 84 coordinates 88 | 89 | ```{code-cell} python 90 | proj = wrl.georef.get_default_projection() 91 | img = img.wrl.georef.georeference(crs=proj) 92 | 93 | pm = img.wrl.vis.plot() 94 | txt = plt.title("Simple PPI in WGS84 coordinates") 95 | ``` 96 | 97 | ## Plotting just one sector 98 | 99 | For this purpose, we need to give the ranges and azimuths explicitly... 100 | 101 | ```{code-cell} python 102 | pm = img[200:251, 40:81].wrl.vis.plot() 103 | txt = plt.title("Sector PPI") 104 | ``` 105 | 106 | ## Adding a crosshair to the PPI 107 | 108 | ```{code-cell} python 109 | # plot the PPI 110 | pm = img.wrl.vis.plot() 111 | # ... plot a crosshair over our data... 112 | ax = plt.gca() 113 | # ... plot a crosshair over our data... 114 | wrl.vis.plot_ppi_crosshair( 115 | site=(img.longitude.values, img.latitude.values, img.altitude.values), 116 | ranges=[50e3, 100e3, 128e3], 117 | angles=[0, 90, 180, 270], 118 | line=dict(color="white"), 119 | circle={"edgecolor": "white"}, 120 | ax=ax, 121 | crs=proj, 122 | ) 123 | plt.title("Offset and Custom Crosshair") 124 | plt.axis("tight") 125 | plt.gca().set_aspect("equal") 126 | ``` 127 | 128 | ## Placing the polar data in a projected Cartesian reference system 129 | 130 | Using the `proj` keyword we tell the function to: 131 | - interpret the site coordinates as longitude/latitude 132 | - reproject the coordinates to the given projection (here: dwd-radolan composite coordinate system) 133 | 134 | ```{code-cell} python 135 | proj_rad = wrl.georef.create_osr("dwd-radolan") 136 | img = img.wrl.georef.georeference(crs=proj_rad) 137 | img.wrl.vis.plot() 138 | ax = plt.gca() 139 | # Now the crosshair ranges must be given in meters 140 | wrl.vis.plot_ppi_crosshair( 141 | site=(img.longitude.values, img.latitude.values, img.altitude.values), 142 | ranges=[40000, 80000, 128000], 143 | line=dict(color="white"), 144 | circle={"edgecolor": "white"}, 145 | ax=ax, 146 | crs=proj_rad, 147 | ) 148 | plt.title("Georeferenced/Projected PPI") 149 | plt.axis("tight") 150 | plt.gca().set_aspect("equal") 151 | ``` 152 | 153 | ## Some side effects of georeferencing 154 | 155 | Transplanting the radar virtually moves it away from the central meridian of the projection (which is 10 degrees east). Due north now does not point straight upwards on the map. 156 | 157 | The crosshair shows this: for the case that the lines should actually become curved, they are implemented as a piecewise linear curve with 10 vertices. The same is true for the range circles, but with more vertices, of course. 158 | 159 | ```{code-cell} python 160 | site = (45.0, 7.0, 0.0) 161 | img["longitude"].values, img["latitude"].values = (45.0, 7.0) 162 | img = img.wrl.georef.georeference(crs=proj_rad) 163 | img.wrl.vis.plot() 164 | ax = plt.gca() 165 | ax = wrl.vis.plot_ppi_crosshair( 166 | site=(img.longitude.values, img.latitude.values, img.altitude.values), 167 | ranges=[64000, 128000], 168 | line=dict(color="red"), 169 | circle={"edgecolor": "red"}, 170 | crs=proj_rad, 171 | ax=ax, 172 | ) 173 | txt = plt.title("Projection Side Effects") 174 | ``` 175 | 176 | ## Plot on Mercator-Map using cartopy 177 | 178 | ```{code-cell} python 179 | map_proj = ccrs.Mercator(central_longitude=site[1]) 180 | ``` 181 | 182 | ```{code-cell} python 183 | img["longitude"].values, img["latitude"].values = (7.0, 45.0) 184 | img = img.wrl.georef.georeference() 185 | fig = plt.figure(figsize=(10, 10)) 186 | img.wrl.vis.plot(crs=map_proj, fig=fig, ax=111) 187 | ax = plt.gca() 188 | ax.gridlines(draw_labels=True) 189 | ``` 190 | 191 | ## More decorations and annotations 192 | 193 | You can annotate these plots by using standard matplotlib methods. 194 | 195 | ```{code-cell} python 196 | pm = img.wrl.vis.plot() 197 | ax = plt.gca() 198 | ylabel = ax.set_xlabel("easting [m]") 199 | ylabel = ax.set_ylabel("northing [m]") 200 | title = ax.set_title("PPI manipulations/colorbar") 201 | # you can now also zoom - either programmatically or interactively 202 | xlim = ax.set_xlim(-80000, -20000) 203 | ylim = ax.set_ylim(-80000, 0) 204 | # as the function returns the axes- and 'mappable'-objects colorbar needs, adding a colorbar is easy 205 | cb = plt.colorbar(pm, ax=ax) 206 | ``` 207 | -------------------------------------------------------------------------------- /notebooks/visualisation/plot_scan_strategy.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: md:myst 4 | text_representation: 5 | extension: .md 6 | format_name: myst 7 | format_version: 0.13 8 | jupytext_version: 1.18.1 9 | kernelspec: 10 | name: python3 11 | display_name: Python 3 12 | --- 13 | 14 | ```{include} ../../_includes/license_block.md 15 | ``` 16 | # Plotting Radar Scan Strategy 17 | 18 | This notebook shows how to plot the scan strategy of a specific radar. 19 | 20 | ```{code-cell} python 21 | import os 22 | import warnings 23 | 24 | import matplotlib.pyplot as plt 25 | import numpy as np 26 | import wradlib as wrl 27 | import wradlib_data 28 | 29 | warnings.filterwarnings("ignore") 30 | ``` 31 | 32 | ## Setup Radar details 33 | 34 | ```{code-cell} python 35 | nrays = 360 36 | nbins = 150 37 | range_res = 500.0 38 | ranges = np.arange(nbins) * range_res 39 | elevs = [28.0, 18.0, 14.0, 11.0, 8.2, 6.0, 4.5, 3.1, 2.0, 1.0] 40 | site = (-28.1, 38.42, 450.0) 41 | beamwidth = 1.0 42 | ``` 43 | 44 | ## Standard Plot 45 | 46 | This works with some assumptions: 47 | 48 | - beamwidth = 1.0 49 | - vert_res = 500.0 50 | - maxalt = 10000.0 51 | - units = 'm' 52 | 53 | ```{code-cell} python 54 | ax = wrl.vis.plot_scan_strategy(ranges, elevs, site) 55 | ``` 56 | 57 | ## Change Plot Style 58 | 59 | 60 | ### Plot Axes in Kilometer 61 | 62 | To quickly change the Axes tickmarks and labels from meter to kilometer, just add keyword argument `units='km'`. 63 | 64 | ```{code-cell} python 65 | ax = wrl.vis.plot_scan_strategy(ranges, elevs, site, units="km") 66 | ``` 67 | 68 | ### Change Axes Resolution and Range 69 | 70 | The horizontal and vertical Axes Resolution and Range can be set by feeding keyword arguments `vert_res`, `maxalt`, `range_res`, `max_range` in meter. 71 | 72 | ```{code-cell} python 73 | ax = wrl.vis.plot_scan_strategy( 74 | ranges, 75 | elevs, 76 | site, 77 | vert_res=1000.0, 78 | maxalt=15000.0, 79 | range_res=5000.0, 80 | maxrange=75000.0, 81 | units="km", 82 | ) 83 | ``` 84 | 85 | ## Change Beamwidth 86 | 87 | The beamwidth defaults to 1.0°. It can specified by keyword argument `beamwidth`. 88 | 89 | ```{code-cell} python 90 | ax = wrl.vis.plot_scan_strategy(ranges, elevs, site, beamwidth=0.5, units="km") 91 | ``` 92 | 93 | ## Change Colors 94 | 95 | The colorcycle can be changed from the default `tab10` to any colormap available in matplotlib. If the output is intended to be plotted as grey-scale, the use of the Perceptually Uniform Sequential colormaps (eg. `viridis`, `cividis`) is suggested. 96 | 97 | ```{code-cell} python 98 | ax = wrl.vis.plot_scan_strategy(ranges, elevs, site, units="km", cmap="viridis") 99 | ``` 100 | 101 | ## Plot Terrain 102 | 103 | A terrain profile can be added to the plot by specifying keyword argument `terrain=True` which automatically downloads neccessary SRTM DEM data and calculates the terrain profile. Additionally the azimuth angle need to be set via keyword argument `az` (it would default to `0`, pointing due north). 104 | 105 | For this to work the `WRADLIB_DATA` environment variable has to point a writable folder. Aditionally users need an [earthdata account](https://urs.earthdata.nasa.gov/) with bearer token. 106 | 107 | ```{code-cell} python 108 | # only run if environment variables are set 109 | has_data = os.environ.get("WRADLIB_EARTHDATA_BEARER_TOKEN", False) 110 | ``` 111 | 112 | ```{code-cell} python 113 | if has_data: 114 | ax = wrl.vis.plot_scan_strategy( 115 | ranges, elevs, site, units="km", terrain=True, az=10 116 | ) 117 | ``` 118 | 119 | Instead of downloading the SRTM data a precomputed terrain profile can be plotted. Just for the purpose to show this, the terrain data is calculated via the same mechanism as in plot_scan_strategy. The profile should be fed via the same keyword argument `terrain`. 120 | 121 | ```{code-cell} python 122 | flist = [ 123 | "geo/N38W028.SRTMGL3.hgt.zip", 124 | "geo/N38W029.SRTMGL3.hgt.zip", 125 | "geo/N39W028.SRTMGL3.hgt.zip", 126 | "geo/N39W029.SRTMGL3.hgt.zip", 127 | ] 128 | [wradlib_data.DATASETS.fetch(f) for f in flist] 129 | xyz, rad = wrl.georef.spherical_to_xyz(ranges, [10.0], elevs, site, squeeze=True) 130 | ll = wrl.georef.reproject(xyz, src_crs=rad) 131 | 132 | # (down-)load srtm data 133 | ds = wrl.io.get_srtm( 134 | [ll[..., 0].min(), ll[..., 0].max(), ll[..., 1].min(), ll[..., 1].max()], 135 | ) 136 | rastervalues, rastercoords, proj = wrl.georef.extract_raster_dataset( 137 | ds, nodata=-32768.0 138 | ) 139 | # map rastervalues to polar grid points 140 | terrain = wrl.ipol.cart_to_irregular_spline( 141 | rastercoords, rastervalues, ll[-1, ..., :2], order=3, prefilter=False 142 | ) 143 | ``` 144 | 145 | ```{code-cell} python 146 | ax = wrl.vis.plot_scan_strategy(ranges, elevs, site, units="km", terrain=terrain) 147 | ``` 148 | 149 | ## Plotting in grids 150 | 151 | The keyword argument `ax` can be used to specify the axes to plot in. 152 | 153 | ```{code-cell} python 154 | if has_data: 155 | fig = plt.figure(figsize=(16, 8)) 156 | ax1 = 221 157 | ax1 = wrl.vis.plot_scan_strategy( 158 | ranges, 159 | elevs, 160 | site, 161 | beamwidth=1.0, 162 | vert_res=500.0, 163 | range_res=5000.0, 164 | maxrange=75000.0, 165 | units="km", 166 | terrain=None, 167 | ax=ax1, 168 | ) 169 | ax2 = 222 170 | ax2 = wrl.vis.plot_scan_strategy( 171 | ranges, 172 | elevs, 173 | site, 174 | beamwidth=1.0, 175 | vert_res=100.0, 176 | maxalt=1000.0, 177 | range_res=1000.0, 178 | maxrange=30000.0, 179 | units="km", 180 | terrain=None, 181 | ax=ax2, 182 | ) 183 | ax3 = 223 184 | ax3 = wrl.vis.plot_scan_strategy( 185 | ranges, 186 | elevs, 187 | site, 188 | beamwidth=1.0, 189 | vert_res=500.0, 190 | range_res=5000.0, 191 | maxrange=75000.0, 192 | units="km", 193 | terrain=True, 194 | az=10, 195 | ax=ax3, 196 | ) 197 | ax4 = 224 198 | ax4 = wrl.vis.plot_scan_strategy( 199 | ranges, 200 | elevs, 201 | site, 202 | beamwidth=1.0, 203 | vert_res=100.0, 204 | maxalt=1000.0, 205 | range_res=1000.0, 206 | maxrange=30000.0, 207 | units="km", 208 | terrain=True, 209 | az=10, 210 | ax=ax4, 211 | ) 212 | plt.tight_layout() 213 | ``` 214 | 215 | ## Plotting with curvelinear grid 216 | 217 | All of the above shown plotting into a cartesian coordinate system is also possible with a curvelinear grid. Just set keyword argument `cg=True`. The thick black line denotes the earth mean sea level (MSL). 218 | 219 | ```{code-cell} python 220 | ax = wrl.vis.plot_scan_strategy( 221 | ranges, elevs, site, units="km", cg=True, terrain=terrain 222 | ) 223 | ``` 224 | -------------------------------------------------------------------------------- /notebooks/fileio/gis/vector_data.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: md:myst 4 | text_representation: 5 | extension: .md 6 | format_name: myst 7 | kernelspec: 8 | name: python3 9 | display_name: Python 3 10 | --- 11 | 12 | ```{include} ../../../_includes/license_block.md 13 | ``` 14 | # Vector Source 15 | 16 | 17 | The {class}`wradlib.io.VectorSource` class is designed to conveniently handle Vector Data (eg. shapefiles). It originates from the {mod}`wradlib.zonalstats` module but moved to {mod}`wradlib.io.gdal` for better visibility. 18 | 19 | - managing georeferenced data (grid points or grid polygons, zonal polygons), 20 | - output to vector and raster files available within ogr/gdal 21 | - geopandas dataframe connector 22 | 23 | ```{code-cell} python 24 | import wradlib as wrl 25 | import matplotlib.pyplot as plt 26 | import matplotlib as mpl 27 | import warnings 28 | import numpy as np 29 | 30 | warnings.filterwarnings("ignore") 31 | 32 | ``` 33 | 34 | The {class}`wradlib.io.VectorSource` class handles point or polygon vector data by wrapping ogr.DataSource with special functions. 35 | 36 | The following example shows how to create different VectorSource objects: 37 | 38 | ```{code-cell} python 39 | from osgeo import osr 40 | 41 | # create gk2 projection osr object 42 | proj_gk2 = osr.SpatialReference() 43 | proj_gk2.ImportFromEPSG(31466) 44 | 45 | # Setting up DataSource 46 | box0 = np.array( 47 | [ 48 | [2600000.0, 5630000.0], 49 | [2600000.0, 5640000.0], 50 | [2610000.0, 5640000.0], 51 | [2610000.0, 5630000.0], 52 | [2600000.0, 5630000.0], 53 | ] 54 | ) 55 | box1 = np.array( 56 | [ 57 | [2610000.0, 5630000.0], 58 | [2610000.0, 5640000.0], 59 | [2620000.0, 5640000.0], 60 | [2620000.0, 5630000.0], 61 | [2610000.0, 5630000.0], 62 | ] 63 | ) 64 | box2 = np.array( 65 | [ 66 | [2600000.0, 5640000.0], 67 | [2600000.0, 5650000.0], 68 | [2610000.0, 5650000.0], 69 | [2610000.0, 5640000.0], 70 | [2600000.0, 5640000.0], 71 | ] 72 | ) 73 | box3 = np.array( 74 | [ 75 | [2610000.0, 5640000.0], 76 | [2610000.0, 5650000.0], 77 | [2620000.0, 5650000.0], 78 | [2620000.0, 5640000.0], 79 | [2610000.0, 5640000.0], 80 | ] 81 | ) 82 | 83 | point0 = np.array(wrl.georef.get_centroid(box0)) 84 | point1 = np.array(wrl.georef.get_centroid(box1)) 85 | point2 = np.array(wrl.georef.get_centroid(box2)) 86 | point3 = np.array(wrl.georef.get_centroid(box3)) 87 | 88 | # creates Polygons in Datasource 89 | poly = wrl.io.VectorSource( 90 | np.array([box0, box1, box2, box3]), trg_crs=proj_gk2, name="poly" 91 | ) 92 | 93 | # creates Points in Datasource 94 | point = wrl.io.VectorSource( 95 | np.vstack((point0, point1, point2, point3)), trg_crs=proj_gk2, name="point" 96 | ) 97 | ``` 98 | 99 | ```{code-cell} python 100 | print(poly) 101 | ``` 102 | 103 | Let's have a look at the data, which will be exported as numpy arrays. The property ``data`` exports all available data as numpy arrays: 104 | 105 | 106 | ## numpy access 107 | 108 | ```{code-cell} python 109 | print(poly.data) 110 | print(point.data) 111 | ``` 112 | 113 | ## geopandas access 114 | 115 | ```{code-cell} python 116 | poly.geo.explore() 117 | ``` 118 | 119 | ```{code-cell} python 120 | point.geo.loc[slice(0, 2)] 121 | ``` 122 | 123 | ```{code-cell} python 124 | point.geo.loc[[0, 1, 3]] 125 | ``` 126 | 127 | ```{code-cell} python 128 | point.geo.query("index in (0, 2)") 129 | ``` 130 | 131 | ```{code-cell} python 132 | fig = plt.figure() 133 | ax = fig.add_subplot(111) 134 | poly.geo.plot(column="index", ax=ax) 135 | point.geo.plot(ax=ax) 136 | ``` 137 | 138 | Now, with the DataSource being created, we can add/set attribute data of the features: 139 | 140 | ```{code-cell} python 141 | # add attribute 142 | poly.set_attribute("mean", np.array([10.1, 20.2, 30.3, 40.4])) 143 | point.set_attribute("mean", np.array([10.1, 20.2, 30.3, 40.4])) 144 | ``` 145 | 146 | Attributes associated with features can also be retrieved: 147 | 148 | ```{code-cell} python 149 | # get attributes 150 | print(poly.get_attributes(["mean"])) 151 | # get attributes filtered 152 | print(poly.get_attributes(["mean"], filt=("index", 2))) 153 | ``` 154 | 155 | Currently data can also be retrieved by: 156 | 157 | - index - {func}`wradlib.io.gdal.VectorSource.get_data_by_idx`, 158 | - attribute - {func}`wradlib.io.gdal.VectorSource.get_data_by_att` and 159 | - geometry - {func}`wradlib.io.gdal.VectorSource.get_data_by_geom`. 160 | 161 | 162 | Using the property `mode` the output type can be set permanently. 163 | 164 | 165 | ## get_data_by_idx 166 | 167 | ```{code-cell} python 168 | point.get_data_by_idx([0, 2]) 169 | ``` 170 | 171 | ```{code-cell} python 172 | point.get_data_by_idx([0, 2], mode="geo") 173 | ``` 174 | 175 | ## get_data_by_att 176 | 177 | ```{code-cell} python 178 | point.get_data_by_att("index", [0, 2]) 179 | ``` 180 | 181 | ```{code-cell} python 182 | point.get_data_by_att("index", [0, 2], mode="geo") 183 | ``` 184 | 185 | ## get_data_by_geom 186 | 187 | ```{code-cell} python 188 | # get OGR.Geometry 189 | geom0 = poly.get_data_by_idx([0], mode="ogr")[0] 190 | # get geopandas Geometry 191 | geom1 = poly.get_data_by_idx([0], mode="geo") 192 | ``` 193 | 194 | ```{code-cell} python 195 | point.get_data_by_geom(geom=geom0) 196 | ``` 197 | 198 | ```{code-cell} python 199 | point.get_data_by_geom(geom=geom0, mode="ogr") 200 | ``` 201 | 202 | ```{code-cell} python 203 | point.get_data_by_geom(geom=geom1, mode="geo") 204 | ``` 205 | 206 | Finally, we can export the contained data to OGR/GDAL supported [vector](https://gdal.org/ogr_formats.html) and [raster](https://gdal.org/formats_list.html) files: 207 | 208 | ```{code-cell} python 209 | # dump as 'ESRI Shapefile', default 210 | poly.dump_vector("test_poly.shp") 211 | point.dump_vector("test_point.shp") 212 | # dump as 'GeoJSON' 213 | poly.dump_vector("test_poly.geojson", driver="GeoJSON") 214 | point.dump_vector("test_point.geojson", driver="GeoJSON") 215 | # dump as 'GTiff', default 216 | poly.dump_raster("test_poly_raster.tif", attr="mean", pixel_size=100.0) 217 | # dump as 'netCDF' 218 | poly.dump_raster("test_poly_raster.nc", driver="netCDF", attr="mean", pixel_size=100.0) 219 | ``` 220 | 221 | ## reload geojson 222 | 223 | ```{code-cell} python 224 | point2 = wrl.io.VectorSource("test_point.geojson") 225 | poly2 = wrl.io.VectorSource("test_poly.geojson") 226 | fig = plt.figure() 227 | ax = fig.add_subplot(111) 228 | poly2.geo.plot(column="index", ax=ax) 229 | point2.geo.plot(ax=ax) 230 | ``` 231 | 232 | ## reload raster geotiff 233 | 234 | ```{code-cell} python 235 | import xarray as xr 236 | 237 | ds = xr.open_dataset("test_poly_raster.tif") 238 | ds.band_data[0].plot() 239 | ``` 240 | 241 | ## reload raster netcdf 242 | 243 | ```{code-cell} python 244 | ds = xr.open_dataset("test_poly_raster.nc") 245 | ds.Band1.plot() 246 | ``` 247 | -------------------------------------------------------------------------------- /notebooks/interpolation/interpolation.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: md:myst 4 | text_representation: 5 | extension: .md 6 | format_name: myst 7 | format_version: 0.13 8 | jupytext_version: 1.18.1 9 | kernelspec: 10 | name: python3 11 | display_name: Python 3 12 | --- 13 | 14 | ```{include} ../../_includes/license_block.md 15 | ``` 16 | # How to use wradlib's ipol module for interpolation tasks? 17 | 18 | ```{code-cell} python 19 | import warnings 20 | 21 | import matplotlib.pyplot as plt 22 | import numpy as np 23 | import wradlib as wrl 24 | import wradlib_data 25 | 26 | warnings.filterwarnings("ignore") 27 | ``` 28 | 29 | ## 1-dimensional example 30 | 31 | Includes Nearest Neighbours, Inverse Distance Weighting, and Ordinary Kriging. 32 | 33 | ```{code-cell} python 34 | # Synthetic observations 35 | xsrc = np.arange(10)[:, None] 36 | vals = np.sin(xsrc).ravel() 37 | 38 | # Define target coordinates 39 | xtrg = np.linspace(0, 20, 100)[:, None] 40 | 41 | # Set up interpolation objects 42 | # IDW 43 | idw = wrl.ipol.Idw(xsrc, xtrg) 44 | # Nearest Neighbours 45 | nn = wrl.ipol.Nearest(xsrc, xtrg) 46 | # Linear 47 | ok = wrl.ipol.OrdinaryKriging(xsrc, xtrg) 48 | 49 | # Plot results 50 | plt.figure(figsize=(10, 5)) 51 | plt.plot(xsrc.ravel(), vals, "bo", label="Observation") 52 | plt.plot(xtrg.ravel(), idw(vals), "r-", label="IDW interpolation") 53 | plt.plot(xtrg.ravel(), nn(vals), "k-", label="Nearest Neighbour interpolation") 54 | plt.plot(xtrg.ravel(), ok(vals), "g-", label="Ordinary Kriging") 55 | plt.xlabel("Distance", fontsize="large") 56 | plt.ylabel("Value", fontsize="large") 57 | plt.legend(loc="lower right") 58 | ``` 59 | 60 | ## 2-dimensional example 61 | 62 | Includes Nearest Neighbours, Inverse Distance Weighting, Linear Interpolation, and Ordinary Kriging. 63 | 64 | ```{code-cell} python 65 | # Synthetic observations and source coordinates 66 | src = np.vstack((np.array([4, 7, 3, 15]), np.array([8, 18, 17, 3]))).transpose() 67 | np.random.seed(1319622840) 68 | vals = np.random.uniform(size=len(src)) 69 | 70 | # Target coordinates 71 | xtrg = np.linspace(0, 20, 40) 72 | ytrg = np.linspace(0, 20, 40) 73 | trg = np.meshgrid(xtrg, ytrg) 74 | trg = np.vstack((trg[0].ravel(), trg[1].ravel())).T 75 | 76 | # Interpolation objects 77 | idw = wrl.ipol.Idw(src, trg) 78 | nn = wrl.ipol.Nearest(src, trg) 79 | linear = wrl.ipol.Linear(src, trg) 80 | ok = wrl.ipol.OrdinaryKriging(src, trg) 81 | 82 | 83 | # Subplot layout 84 | def gridplot(interpolated, title=""): 85 | ax.pcolormesh(xtrg, ytrg, interpolated.reshape((len(xtrg), len(ytrg)))) 86 | plt.axis("tight") 87 | ax.scatter(src[:, 0], src[:, 1], facecolor="None", s=50, marker="s") 88 | plt.title(title) 89 | plt.xlabel("x coordinate") 90 | plt.ylabel("y coordinate") 91 | 92 | 93 | # Plot results 94 | fig = plt.figure(figsize=(8, 8)) 95 | ax = fig.add_subplot(221, aspect="equal") 96 | gridplot(idw(vals), "IDW") 97 | ax = fig.add_subplot(222, aspect="equal") 98 | gridplot(nn(vals), "Nearest Neighbours") 99 | ax = fig.add_subplot(223, aspect="equal") 100 | gridplot(np.ma.masked_invalid(linear(vals)), "Linear interpolation") 101 | ax = fig.add_subplot(224, aspect="equal") 102 | gridplot(ok(vals), "Ordinary Kriging") 103 | plt.tight_layout() 104 | ``` 105 | 106 | ## Using the convenience function ipol.interpolation in order to deal with missing values 107 | 108 | **(1)** Exemplified for one dimension in space and two dimensions of the source value array (could e.g. be two time steps). 109 | 110 | ```{code-cell} python 111 | # Synthetic observations (e.g. two time steps) 112 | src = np.arange(10)[:, None] 113 | vals = np.hstack((1.0 + np.sin(src), 5.0 + 2.0 * np.sin(src))) 114 | # Target coordinates 115 | trg = np.linspace(0, 20, 100)[:, None] 116 | # Here we introduce missing values in the second dimension of the source value array 117 | vals[3:5, 1] = np.nan 118 | # interpolation using the convenience function "interpolate" 119 | idw_result = wrl.ipol.interpolate(src, trg, vals, wrl.ipol.Idw, nnearest=4) 120 | nn_result = wrl.ipol.interpolate(src, trg, vals, wrl.ipol.Nearest) 121 | # Plot results 122 | fig = plt.figure(figsize=(10, 5)) 123 | ax = fig.add_subplot(111) 124 | pl1 = ax.plot(trg, idw_result, "b-", label="IDW") 125 | pl2 = ax.plot(trg, nn_result, "k-", label="Nearest Neighbour") 126 | pl3 = ax.plot(src, vals, "ro", label="Observations") 127 | ``` 128 | 129 | **(2)** Exemplified for two dimensions in space and two dimensions of the source value array (e.g. time steps), containing also NaN values (here we only use IDW interpolation) 130 | 131 | ```{code-cell} python 132 | # Just a helper function for repeated subplots 133 | 134 | 135 | def plotall(ax, trgx, trgy, src, interp, pts, title, vmin, vmax): 136 | ix = np.where(np.isfinite(pts)) 137 | ax.pcolormesh( 138 | trgx, trgy, interp.reshape((len(trgx), len(trgy))), vmin=vmin, vmax=vmax 139 | ) 140 | ax.scatter( 141 | src[ix, 0].ravel(), 142 | src[ix, 1].ravel(), 143 | c=pts.ravel()[ix], 144 | s=20, 145 | marker="s", 146 | vmin=vmin, 147 | vmax=vmax, 148 | ) 149 | ax.set_title(title) 150 | plt.axis("tight") 151 | ``` 152 | 153 | ```{code-cell} python 154 | # Synthetic observations 155 | src = np.vstack((np.array([4, 7, 3, 15]), np.array([8, 18, 17, 3]))).T 156 | np.random.seed(1319622840 + 1) 157 | vals = np.round(np.random.uniform(size=(len(src), 2)), 1) 158 | 159 | # Target coordinates 160 | trgx = np.linspace(0, 20, 100) 161 | trgy = np.linspace(0, 20, 100) 162 | trg = np.meshgrid(trgx, trgy) 163 | trg = np.vstack((trg[0].ravel(), trg[1].ravel())).transpose() 164 | 165 | result = wrl.ipol.interpolate(src, trg, vals, wrl.ipol.Idw, nnearest=4) 166 | 167 | # Now introduce NaNs in the observations 168 | vals_with_nan = vals.copy() 169 | vals_with_nan[1, 0] = np.nan 170 | vals_with_nan[1:3, 1] = np.nan 171 | result_with_nan = wrl.ipol.interpolate( 172 | src, trg, vals_with_nan, wrl.ipol.Idw, nnearest=4 173 | ) 174 | vmin = np.concatenate((vals.ravel(), result.ravel())).min() 175 | vmax = np.concatenate((vals.ravel(), result.ravel())).max() 176 | 177 | fig = plt.figure(figsize=(8, 8)) 178 | ax = fig.add_subplot(221) 179 | plotall(ax, trgx, trgy, src, result[:, 0], vals[:, 0], "1st dim: no NaNs", vmin, vmax) 180 | ax = fig.add_subplot(222) 181 | plotall(ax, trgx, trgy, src, result[:, 1], vals[:, 1], "2nd dim: no NaNs", vmin, vmax) 182 | ax = fig.add_subplot(223) 183 | plotall( 184 | ax, 185 | trgx, 186 | trgy, 187 | src, 188 | result_with_nan[:, 0], 189 | vals_with_nan[:, 0], 190 | "1st dim: one NaN", 191 | vmin, 192 | vmax, 193 | ) 194 | ax = fig.add_subplot(224) 195 | plotall( 196 | ax, 197 | trgx, 198 | trgy, 199 | src, 200 | result_with_nan[:, 1], 201 | vals_with_nan[:, 1], 202 | "2nd dim: two NaN", 203 | vmin, 204 | vmax, 205 | ) 206 | plt.tight_layout() 207 | ``` 208 | 209 | ## How to use interpolation for gridding data in polar coordinates? 210 | 211 | 212 | Read polar coordinates and corresponding rainfall intensity from file 213 | 214 | ```{code-cell} python 215 | filename = wradlib_data.DATASETS.fetch("misc/bin_coords_tur.gz") 216 | src = np.loadtxt(filename) 217 | 218 | filename = wradlib_data.DATASETS.fetch("misc/polar_R_tur.gz") 219 | vals = np.loadtxt(filename) 220 | ``` 221 | 222 | ```{code-cell} python 223 | src.shape 224 | ``` 225 | 226 | Define target grid coordinates 227 | 228 | ```{code-cell} python 229 | xtrg = np.linspace(src[:, 0].min(), src[:, 0].max(), 200) 230 | ytrg = np.linspace(src[:, 1].min(), src[:, 1].max(), 200) 231 | trg = np.meshgrid(xtrg, ytrg) 232 | trg = np.vstack((trg[0].ravel(), trg[1].ravel())).T 233 | ``` 234 | 235 | Linear Interpolation 236 | 237 | ```{code-cell} python 238 | ip_lin = wrl.ipol.Linear(src, trg) 239 | result_lin = ip_lin(vals.ravel(), fill_value=np.nan) 240 | ``` 241 | 242 | IDW interpolation 243 | 244 | ```{code-cell} python 245 | ip_near = wrl.ipol.Nearest(src, trg) 246 | maxdist = trg[1, 0] - trg[0, 0] 247 | result_near = ip_near(vals.ravel(), maxdist=maxdist) 248 | ``` 249 | 250 | Plot results 251 | 252 | ```{code-cell} python 253 | fig = plt.figure(figsize=(15, 6)) 254 | fig.subplots_adjust(wspace=0.4) 255 | ax = fig.add_subplot(131, aspect="equal") 256 | vals = wrl.georef.create_xarray_dataarray(vals).wrl.georef.georeference() 257 | vals.wrl.vis.plot(ax=ax) 258 | ax = fig.add_subplot(132, aspect="equal") 259 | plt.pcolormesh(xtrg, ytrg, result_lin.reshape((len(xtrg), len(ytrg)))) 260 | ax = fig.add_subplot(133, aspect="equal") 261 | plt.pcolormesh(xtrg, ytrg, result_near.reshape((len(xtrg), len(ytrg)))) 262 | ``` 263 | -------------------------------------------------------------------------------- /notebooks/workflow/recipe1.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: md:myst 4 | text_representation: 5 | extension: .md 6 | format_name: myst 7 | format_version: 0.13 8 | jupytext_version: 1.18.1 9 | kernelspec: 10 | name: python3 11 | display_name: Python 3 12 | --- 13 | 14 | ```{include} ../../_includes/license_block.md 15 | ``` 16 | # Recipe #1: Clutter and attenuation correction plus composition for two DWD radars 17 | 18 | 19 | This recipe shows a workflow to process radar data provided by the German Weather Service (DWD). The processing includes: 20 | 21 | - *(1)* Reading local [DX-Data](../fileio/legacy/read_dx) for radars Feldberg and Tuerkheim. 22 | - *(2)* Clutter correction using the Gabell Filter from {mod}`wradlib.classify`. 23 | - *(3)* Attenuation correction using the modified Kraemer algorithm from {mod}`wradlib.atten`. 24 | - *(4)* Conversion from reflectivity to rainfall using the Z-R Conversions from {mod}`wradlib.zr` module. 25 | - *(5)* Accumulation of rainfall depths over the entire event. 26 | - *(6)* Composition of data from both radars to a common Cartesian grid (UTM Zone 32), see {mod}`wradlib.comp`. Composition is based on a weighted combination, using the sampling volume as a quality criterion, see {mod}`wradlib.qual`. 27 | - *(7)* Plotting a rainfall map using cartesian plot, see {mod}`wradlib.vis`. 28 | 29 | ```{code-cell} python 30 | import datetime as dt 31 | import glob 32 | import os 33 | import shutil 34 | import warnings 35 | import zipfile 36 | 37 | import matplotlib.pyplot as plt 38 | import numpy as np 39 | import wradlib as wrl 40 | import wradlib_data 41 | import xarray as xr 42 | 43 | warnings.filterwarnings("ignore") 44 | ``` 45 | 46 | ```{code-cell} python 47 | def read_data(flist, site): 48 | """Helper function to read raw data for a list of datetimes """ 49 | dalist = [] 50 | for f in flist: 51 | data, attrs = wrl.io.read_dx(f) 52 | dtime = dt.datetime.strptime(os.path.basename(f)[15:25], "%y%m%d%H%M") 53 | dalist.append( 54 | wrl.georef.create_xarray_dataarray( 55 | data, 56 | r=np.arange(500, data.shape[1] * 1000 + 500, 1000), 57 | phi=attrs["azim"], 58 | theta=attrs["elev"], 59 | site=site, 60 | sweep_mode="azimuth_surveillance", 61 | ).assign_coords(time=dtime) 62 | ) 63 | ds = xr.concat(dalist, "time") 64 | return ds.assign_coords(elevation=ds.elevation.median("time")) 65 | 66 | 67 | def process_polar_level_data(radarname, site): 68 | """Reading and processing polar level data (DX) for radar """ 69 | print("Polar level processing for radar %s..." % radarname) 70 | 71 | # preparations for loading sample data in source directory 72 | files = glob.glob( 73 | os.path.join( 74 | wrl.util.get_wradlib_data_path(), f"dx/recipe1_data/raa*{radarname}*bin" 75 | ) 76 | ) 77 | 78 | if len(files) == 0: 79 | print( 80 | "WARNING: No data files found - maybe you did not extract " 81 | "the data from data/recipe1_data.zip?" 82 | ) 83 | # loading the data (two hours of 5-minute images) 84 | data = read_data(files, site) 85 | # Clutter filter on an event base 86 | clmap = wrl.classify.filter_gabella(data.mean("time"), tr1=12, n_p=6, tr2=1.1) 87 | data_ipol = wrl.ipol.interpolate_polar(data, mask=clmap) 88 | # correcting for attenuation 89 | pia = data_ipol.wrl.atten.correct_attenuation_constrained( 90 | a_max=1.67e-4, 91 | a_min=2.33e-5, 92 | n_a=100, 93 | b_max=0.7, 94 | b_min=0.65, 95 | n_b=6, 96 | gate_length=1.0, 97 | constraints=[wrl.atten.constraint_dbz, wrl.atten.constraint_pia], 98 | constraint_args=[[59.0], [10.0]], 99 | ) 100 | data_atten = data_ipol + pia 101 | # converting to precipitation depth 102 | R = wrl.zr.z_to_r(wrl.trafo.idecibel(data_atten), a=256, b=1.4) 103 | depth = wrl.trafo.r_to_depth(R, 300.0) 104 | depth.attrs = R.attrs 105 | # calculate hourly accumulation 106 | accum = depth.sum("time") 107 | accum.attrs = { 108 | "standard_name": "rainfall_amount", 109 | "long_name": "rainfall_amount", 110 | "short_name": "RSUM", 111 | "units": "mm", 112 | } 113 | 114 | return accum 115 | ``` 116 | 117 | ```{code-cell} python 118 | def bbox(*args): 119 | """Get bounding box from a set of radar bin coordinates""" 120 | xy = np.array( 121 | [ 122 | [ 123 | arg.x.min().values, 124 | arg.x.max().values, 125 | arg.y.min().values, 126 | arg.y.max().values, 127 | ] 128 | for arg in args 129 | ] 130 | ) 131 | xmin = xy[..., 0].min() 132 | xmax = xy[..., 1].max() 133 | ymin = xy[..., 2].min() 134 | ymax = xy[..., 3].max() 135 | 136 | return xmin, xmax, ymin, ymax 137 | ``` 138 | 139 | ```{code-cell} python 140 | # set timer 141 | start = dt.datetime.now() 142 | # unzip data 143 | filename = wradlib_data.DATASETS.fetch("dx/recipe1_data.zip") 144 | targetdir = wradlib_data.DATASETS.abspath / "dx/recipe1_data" 145 | with zipfile.ZipFile(filename, "r") as z: 146 | z.extractall(targetdir) 147 | 148 | # set scan geometry and radar coordinates 149 | # r = np.arange(500.0, 128500.0, 1000.0) 150 | # az = np.arange(0, 360) 151 | tur_sitecoords = (9.7839, 48.5861, 0) 152 | fbg_sitecoords = (8.005, 47.8744, 0) 153 | 154 | # processing polar level radar data 155 | # Tuerkheim 156 | tur_accum = process_polar_level_data("tur", site=tur_sitecoords) 157 | # Feldberg 158 | fbg_accum = process_polar_level_data("fbg", site=fbg_sitecoords) 159 | ``` 160 | 161 | ```{code-cell} python 162 | # remove unzipped files 163 | if os.path.exists(targetdir): 164 | try: 165 | shutil.rmtree(targetdir) 166 | except Exception: 167 | print("WARNING: Could not remove directory data/recipe1_data") 168 | 169 | # derive UTM Zone 32 coordinates of range-bin centroids 170 | # create osr projection using epsg number for UTM Zone 32 171 | proj_utm = wrl.georef.epsg_to_osr(32632) 172 | ``` 173 | 174 | ```{code-cell} python 175 | tur_accum = tur_accum.wrl.georef.georeference(crs=proj_utm) 176 | fbg_accum = fbg_accum.wrl.georef.georeference(crs=proj_utm) 177 | ``` 178 | 179 | ```{code-cell} python 180 | # define target grid for composition 181 | xmin, xmax, ymin, ymax = bbox(tur_accum, fbg_accum) 182 | x = np.linspace(xmin, xmax + 1000.0, 1000) 183 | y = np.linspace(ymin, ymax + 1000.0, 1000) 184 | grid_coords = wrl.util.gridaspoints(y, x) 185 | cart = xr.Dataset(coords={"x": (["x"], x), "y": (["y"], y)}) 186 | ``` 187 | 188 | ```{code-cell} python 189 | # quality index 190 | tur_pv = tur_accum.wrl.qual.pulse_volume(1000.0, 1.0) 191 | fbg_pv = fbg_accum.wrl.qual.pulse_volume(1000.0, 1.0) 192 | ``` 193 | 194 | ```{code-cell} python 195 | tur_gridded = tur_accum.wrl.comp.togrid( 196 | cart, 197 | radius=128500.0, 198 | center=(tur_accum.y.mean(), tur_accum.x.mean()), 199 | interpol=wrl.ipol.Nearest, 200 | ) 201 | tur_quality_gridded = tur_pv.wrl.comp.togrid( 202 | cart, 203 | radius=128500.0, 204 | center=(tur_pv.y.mean(), tur_pv.x.mean()), 205 | interpol=wrl.ipol.Nearest, 206 | ) 207 | fbg_gridded = fbg_accum.wrl.comp.togrid( 208 | cart, 209 | radius=128500.0, 210 | center=(fbg_accum.y.mean(), fbg_accum.x.mean()), 211 | interpol=wrl.ipol.Nearest, 212 | ) 213 | fbg_quality_gridded = fbg_pv.wrl.comp.togrid( 214 | cart, 215 | radius=128500.0, 216 | center=(fbg_pv.y.mean(), fbg_pv.x.mean()), 217 | interpol=wrl.ipol.Nearest, 218 | ) 219 | ``` 220 | 221 | ```{code-cell} python 222 | fig = plt.figure(figsize=(12, 4)) 223 | ax1 = fig.add_subplot(121) 224 | fbg_gridded.plot(ax=ax1) 225 | ax2 = fig.add_subplot(122) 226 | tur_gridded.plot(ax=ax2) 227 | ``` 228 | 229 | ```{code-cell} python 230 | # compose the both radar-data based on the quality information 231 | # calculated above 232 | radar = xr.DataArray(["tur", "fbg"], dims="radar") 233 | radargrids = xr.concat([tur_gridded, fbg_gridded], dim=radar) 234 | qualitygrids = xr.concat( 235 | [1.0 / (tur_quality_gridded + 0.001), 1.0 / (fbg_quality_gridded + 0.001)], 236 | dim=radar, 237 | ) 238 | ``` 239 | 240 | ```{code-cell} python 241 | print("Composing Tuerkheim and Feldbarg data on a common grid...") 242 | composite = radargrids.wrl.comp.compose_weighted(qualitygrids) 243 | 244 | print("Processing took:", dt.datetime.now() - start) 245 | ``` 246 | 247 | ```{code-cell} python 248 | # Plotting rainfall map 249 | plt.figure(figsize=(10, 8)) 250 | ax = fig.add_subplot(111, aspect="equal") 251 | composite.plot(cmap="viridis") 252 | ax.grid() 253 | ax.set_xlim(min(x), max(x)) 254 | ax.set_ylim(min(y), max(y)) 255 | ``` 256 | 257 | Download required data at the [wradlib-data repository](https://github.com/wradlib/wradlib-data/archive/main.zip). 258 | 259 | ```{note} 260 | In order to run the recipe code, you need to extract the sample data into a directory pointed to by environment variable ``WRADLIB_DATA``. 261 | ``` 262 | -------------------------------------------------------------------------------- /notebooks/zonalstats/zonalstats.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: md:myst 4 | text_representation: 5 | extension: .md 6 | format_name: myst 7 | format_version: 0.13 8 | jupytext_version: 1.18.1 9 | kernelspec: 10 | name: python3 11 | display_name: Python 3 12 | --- 13 | 14 | ```{include} ../../_includes/license_block.md 15 | ``` 16 | # Zonal Statistics - Overview 17 | 18 | ```{toctree} 19 | :hidden: 20 | :maxdepth: 2 21 | Quickstart 22 | Cartesian Grid <../workflow/recipe5> 23 | Polar Grid <../workflow/recipe6> 24 | ``` 25 | 26 | The {mod}`wradlib.zonalstats` module provides classes and functions for calculation of zonal statistics for data on arbitrary grids and projections. 27 | 28 | It provides classes for: 29 | 30 | - managing georeferenced data (grid points or grid polygons, zonal polygons), 31 | - calculation of geographic intersections and managing resulting vector data 32 | - calculation of zonal statistics and managing result data as vector attributes 33 | - output to vector and raster files available within ogr/gdal 34 | 35 | ```{code-cell} python 36 | import warnings 37 | 38 | import numpy as np 39 | import wradlib as wrl 40 | from osgeo import osr 41 | 42 | warnings.filterwarnings("ignore") 43 | ``` 44 | 45 | ## VectorSource 46 | 47 | 48 | The {class}`wradlib.io.gdal.VectorSource` class handles point or polygon vector data by wrapping ogr.DataSource with special functions. It's just a wrapper around the {class}`wradlib.io.gdal.VectorSource` class. 49 | 50 | The following example shows how to create different DataSource objects: 51 | 52 | ```{code-cell} python 53 | # create gauss-krueger2 srs object 54 | proj_gk2 = osr.SpatialReference() 55 | proj_gk2.ImportFromEPSG(31466) 56 | 57 | # Setting up DataSource 58 | box0 = np.array( 59 | [ 60 | [2600000.0, 5630000.0], 61 | [2600000.0, 5640000.0], 62 | [2610000.0, 5640000.0], 63 | [2610000.0, 5630000.0], 64 | [2600000.0, 5630000.0], 65 | ] 66 | ) 67 | box1 = np.array( 68 | [ 69 | [2610000.0, 5630000.0], 70 | [2610000.0, 5640000.0], 71 | [2620000.0, 5640000.0], 72 | [2620000.0, 5630000.0], 73 | [2610000.0, 5630000.0], 74 | ] 75 | ) 76 | box2 = np.array( 77 | [ 78 | [2600000.0, 5640000.0], 79 | [2600000.0, 5650000.0], 80 | [2610000.0, 5650000.0], 81 | [2610000.0, 5640000.0], 82 | [2600000.0, 5640000.0], 83 | ] 84 | ) 85 | box3 = np.array( 86 | [ 87 | [2610000.0, 5640000.0], 88 | [2610000.0, 5650000.0], 89 | [2620000.0, 5650000.0], 90 | [2620000.0, 5640000.0], 91 | [2610000.0, 5640000.0], 92 | ] 93 | ) 94 | 95 | point0 = np.array(wrl.georef.get_centroid(box0)) 96 | point1 = np.array(wrl.georef.get_centroid(box1)) 97 | point2 = np.array(wrl.georef.get_centroid(box2)) 98 | point3 = np.array(wrl.georef.get_centroid(box3)) 99 | 100 | # creates Polygons in Datasource 101 | poly = wrl.io.VectorSource( 102 | np.array([box0, box1, box2, box3]), trg_crs=proj_gk2, name="poly" 103 | ) 104 | 105 | # creates Points in Datasource 106 | point = wrl.io.VectorSource( 107 | np.vstack((point0, point1, point2, point3)), trg_crs=proj_gk2, name="point" 108 | ) 109 | ``` 110 | 111 | Let's have a look at the data, which will be exported as numpy arrays. The property ``data`` exports all available data: 112 | 113 | ```{code-cell} python 114 | print(poly.data) 115 | print(point.data) 116 | ``` 117 | 118 | Currently data can also be retrieved by: 119 | 120 | - index - {func}`wradlib.io.gdal.VectorSource.get_data_by_idx`, 121 | - attribute - {func}`wradlib.io.gdal.VectorSource.get_data_by_att` and 122 | - geometry - {func}`wradlib.io.gdal.VectorSource.get_data_by_geom`. 123 | 124 | Now, with the DataSource being created, we can add/set attribute data of the features: 125 | 126 | ```{code-cell} python 127 | # add attribute 128 | poly.set_attribute("mean", np.array([10.1, 20.2, 30.3, 40.4])) 129 | point.set_attribute("mean", np.array([10.1, 20.2, 30.3, 40.4])) 130 | ``` 131 | 132 | Attributes associated with features can also be retrieved: 133 | 134 | ```{code-cell} python 135 | # get attributes 136 | print(poly.get_attributes(["mean"])) 137 | # get attributes filtered 138 | print(poly.get_attributes(["mean"], filt=("index", 2))) 139 | ``` 140 | 141 | Finally, we can export the contained data to OGR/GDAL supported [vector](https://gdal.org/ogr_formats.html) and [raster](https://gdal.org/formats_list.html) files: 142 | 143 | ```{code-cell} python 144 | # dump as 'ESRI Shapefile', default 145 | poly.dump_vector("test_poly.shp") 146 | point.dump_vector("test_point.shp") 147 | # dump as 'GeoJSON' 148 | poly.dump_vector("test_poly.geojson", driver="GeoJSON") 149 | point.dump_vector("test_point.geojson", driver="GeoJSON") 150 | # dump as 'GTiff', default 151 | poly.dump_raster("test_poly_raster.tif", attr="mean", pixel_size=100.0) 152 | # dump as 'netCDF' 153 | poly.dump_raster("test_poly_raster.nc", driver="netCDF", attr="mean", pixel_size=100.0) 154 | ``` 155 | 156 | ## ZonalData 157 | 158 | 159 | ZonalData is usually available as georeferenced regular gridded data. Here the {class}`wradlib.zonalstats.ZonalDataBase` class manages the grid data, the zonal data (target polygons) and the intersection data of source grid and target polygons. 160 | Because the calculation of intersection is different for point grids and polygon grids, we have subclasses {class}`wradlib.zonalstats.ZonalDataPoly` and {class}`wradlib.zonalstats.ZonalDataPoint`. 161 | 162 | Basically, {class}`wradlib.zonalstats.ZonalDataBase` encapsulates three {class}`wradlib.zonalstats.DataSource` objects: 163 | 164 | - source grid (points/polygons) 165 | - target polygons 166 | - destination (intersection) (points/polygons) 167 | 168 | The destination DataSource object is created from the provided source grid and target polygons at initialisation time. 169 | 170 | 171 | As an example the creation of a {class}`wradlib.zonalstats.ZonalDataPoly` class instance is shown: 172 | 173 | ```{code-cell} python 174 | # setup test grid and catchment 175 | lon = 7.071664 176 | lat = 50.730521 177 | alt = 0 178 | r = np.array(range(50, 100 * 1000 + 50, 100)) 179 | a = np.array(range(0, 90, 1)) 180 | rays = a.shape[0] 181 | bins = r.shape[0] 182 | 183 | # setup OSR objects 184 | proj_utm = osr.SpatialReference() 185 | proj_utm.ImportFromEPSG(32632) 186 | 187 | # create polar grid polygon vertices in UTM 188 | radar_utm = wrl.georef.spherical_to_polyvert(r, a, 0, (lon, lat, alt), crs=proj_utm) 189 | radar_utm = radar_utm[..., 0:2] 190 | # reshape 191 | radar_utm.shape = (rays * bins, 5, 2) 192 | 193 | box0 = np.array( 194 | [ 195 | [390000.0, 5630000.0], 196 | [390000.0, 5640000.0], 197 | [400000.0, 5640000.0], 198 | [400000.0, 5630000.0], 199 | [390000.0, 5630000.0], 200 | ] 201 | ) 202 | 203 | box1 = np.array( 204 | [ 205 | [400000.0, 5630000.0], 206 | [400000.0, 5640000.0], 207 | [410000.0, 5640000.0], 208 | [410000.0, 5630000.0], 209 | [400000.0, 5630000.0], 210 | ] 211 | ) 212 | 213 | targets = np.array([box0, box1]) 214 | 215 | zdpoly = wrl.zonalstats.ZonalDataPoly(radar_utm, trg=targets, crs=proj_utm) 216 | ``` 217 | 218 | When calculating the intersection, also weights are calculated for every source grid feature and attributed to the destination features. 219 | 220 | With the property ``isecs`` it is possible to retrieve the intersection geometries as numpy array, further get-functions add to the functionality: 221 | 222 | ```{code-cell} python 223 | # get intersections as numpy array 224 | isecs = zdpoly.isecs 225 | # get intersections for target polygon 0 226 | isec0 = zdpoly.get_isec(0) 227 | # get source indices referring to target polygon 0 228 | ind0 = zdpoly.get_source_index(0) 229 | 230 | print(isecs.shape, isec0.shape, ind0.shape) 231 | ``` 232 | 233 | There are import/export functions using [ESRI-Shapfile Format](https://de.wikipedia.org/wiki/Shapefile) as data format. Next export and import is shown: 234 | 235 | ```{code-cell} python 236 | zdpoly.dump_vector("test_zdpoly") 237 | zdpoly_new = wrl.zonalstats.ZonalDataPoly("test_zdpoly") 238 | ``` 239 | 240 | ## ZonalStats 241 | 242 | For ZonalStats the {class}`wradlib.zonalstats.ZonalStatsBase` class and the two subclasses {class}`wradlib.zonalstats.ZonalStatsPoly` and {class}`wradlib.zonalstats.ZonalStatsPoint` are available. ZonalStatsBase encapsulates one ZonalData object. Properties for simple access of ZonalData, intersection indices and weights are provided. The following code will add ``mean`` and ``var`` attributes to the target DataSource: 243 | 244 | ```{code-cell} python 245 | # create ZonalStatsPoly instance 246 | gc = wrl.zonalstats.ZonalStatsPoly(zdpoly_new) 247 | # create some artificial data for processing using the features indices 248 | count = radar_utm.shape[0] 249 | data = 1000000.0 / np.array(range(count)) 250 | # calculate mean and variance 251 | mean = gc.mean(data) 252 | var = gc.var(data) 253 | 254 | print("Average:", mean) 255 | print("Variance:", var) 256 | ``` 257 | 258 | Next we can export the resulting zonal statistics to vector and raster files: 259 | 260 | ```{code-cell} python 261 | # export to vector GeoJSON 262 | gc.zdata.trg.dump_vector("test_zonal_json.geojson", driver="GeoJSON") 263 | # export 'mean' to raster netCDF 264 | gc.zdata.trg.dump_raster( 265 | "test_zonal_hdr.nc", driver="netCDF", attr="mean", pixel_size=100.0 266 | ) 267 | ``` 268 | 269 | The ZonalStats classes can also be used without any ZonalData by instantiating with precalculated index and weight values. Be sure to use matching ix, w and data arrays: 270 | 271 | ```{code-cell} python 272 | # get ix, and weight arrays 273 | ix = gc.ix 274 | w = gc.w 275 | # instantiate new ZonlaStats object 276 | gc1 = wrl.zonalstats.ZonalStatsPoly(ix=ix, w=w) 277 | # caclulate statistics 278 | avg = gc1.mean(data) 279 | var = gc1.var(data) 280 | 281 | print("Average:", avg) 282 | print("Variance:", var) 283 | ``` 284 | -------------------------------------------------------------------------------- /notebooks/classify/hmcp_nexrad.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: md:myst 4 | text_representation: 5 | extension: .md 6 | format_name: myst 7 | format_version: 0.13 8 | jupytext_version: 1.18.1 9 | kernelspec: 10 | name: python3 11 | display_name: Python 3 12 | --- 13 | 14 | ```{include} ../../_includes/license_block.md 15 | ``` 16 | # Hydrometeor partitioning ratio retrievals for Ground Radar 17 | 18 | In this notebook, measurements from NEXRAD's KDDC ground radar are used to derive Hydrometeor Partitioning Ratios (HPR) following Pejcic et al. 2025 (in review). This requires the horizontal reflectivity, differential reflectivity, specific differential phase, cross correlation coefficient, temperature information and rain type. The temperature information is derived from sounding and a rain type classification is applied following Park et al. The HPRs for the different hydrometeor classes are then presented. 19 | 20 | ```{code-cell} python 21 | import datetime as dt 22 | import urllib 23 | import warnings 24 | 25 | import matplotlib.pyplot as plt 26 | import numpy as np 27 | import wradlib as wrl 28 | import wradlib_data 29 | import xarray as xr 30 | import xradar as xd 31 | from IPython.display import display 32 | from scipy import spatial 33 | 34 | warnings.filterwarnings("ignore") 35 | ``` 36 | 37 | ## Read centroids, covariances and weights 38 | 39 | ```{code-cell} python 40 | cdp_file = wradlib_data.DATASETS.fetch("misc/hmcp_centroids_dp.nc") 41 | with xr.open_dataset(cdp_file) as cdp: 42 | pass 43 | cdp 44 | ``` 45 | 46 | ```{code-cell} python 47 | weights_file = wradlib_data.DATASETS.fetch("misc/hmcp_weights.nc") 48 | with xr.open_dataset(weights_file) as cw: 49 | pass 50 | cw 51 | ``` 52 | 53 | ## Read polarimetric radar observations 54 | 55 | ```{code-cell} python 56 | volume = wradlib_data.DATASETS.fetch("netcdf/KDDC_2018_0625_051138_min.cf") 57 | gr_data = xd.io.open_cfradial1_datatree(volume) 58 | gr_data 59 | ``` 60 | 61 | ## Get Temperature Profile 62 | 63 | We would need the temperature of each radar bin. For that, we use Sounding Data. We also set the max_height to 30km and interpolate the vertical profile with a resolution of 1m. 64 | 65 | ```{code-cell} python 66 | rs_time = dt.datetime.fromisoformat( 67 | str(gr_data.time_coverage_start.values.item().decode()) 68 | ) 69 | wmoid = 72451 70 | 71 | try: 72 | rs_ds = wrl.io.get_radiosonde( 73 | wmoid, rs_time, cols=np.arange(13), xarray=True, max_height=30000.0, res=1.0 74 | ) 75 | except (urllib.error.HTTPError, urllib.error.URLError): 76 | print("service down") 77 | dataf = wradlib_data.DATASETS.fetch("misc/radiosonde_72451_20180625_0000.h5") 78 | rs_data, _ = wrl.io.from_hdf5(dataf) 79 | metaf = wradlib_data.DATASETS.fetch("misc/radiosonde_72451_20180625_0000.json") 80 | with open(metaf, "r") as infile: 81 | import json 82 | 83 | rs_meta = json.load(infile) 84 | rs_ds = wrl.io.radiosonde_to_xarray( 85 | rs_data, meta=rs_meta, max_height=30000.0, res=1.0 86 | ) 87 | ``` 88 | 89 | ```{code-cell} python 90 | display(rs_ds) 91 | ``` 92 | 93 | ## Plot Temperature Profile 94 | 95 | ```{code-cell} python 96 | fig = plt.figure(figsize=(5, 10)) 97 | ax = fig.add_subplot(111) 98 | rs_ds.TEMP.plot(y="HGHT", ax=ax, zorder=0, c="r") 99 | ax.grid(True) 100 | ``` 101 | 102 | ## get freezing level height 103 | 104 | We need to obtain the freezing level height, which is needed for an ad-hoc retrieval of raintype. 105 | 106 | ```{code-cell} python 107 | fl = np.abs(rs_ds).argmin("HGHT").TEMP 108 | display(fl) 109 | ``` 110 | 111 | ## georeference DataTree 112 | 113 | For the interpolation of the temperature sounding data onto the radar sweeps, we need the xyz coordinates of the sweeps. 114 | 115 | ```{code-cell} python 116 | gr_data2 = gr_data.xradar.georeference() 117 | ``` 118 | 119 | ```{code-cell} python 120 | gr_data2["sweep_0"] 121 | ``` 122 | 123 | ## Interpolate Temperature onto sweeps 124 | 125 | The following function interpolates the vertical temperature profile onto the radar sweeps. 126 | 127 | ```{code-cell} python 128 | def merge_radar_profile(rds, cds): 129 | if "z" in rds.coords: 130 | cds = cds.interp({"HGHT": rds.z}, method="linear") 131 | rds = rds.assign({"TEMP": cds}) 132 | return rds 133 | 134 | 135 | gr_data3 = gr_data2.map_over_datasets(merge_radar_profile, rs_ds.TEMP) 136 | ``` 137 | 138 | ```{code-cell} python 139 | gr_data3["sweep_1"].TEMP.plot(x="x", y="y") 140 | ``` 141 | 142 | ## Ad-hoc retrieval of raintype 143 | 144 | 145 | The following algorithm of raintype estimation is derived after ([Park et al.](https://doi.org/10.1175/2008WAF2222205.1)). 146 | 147 | 1. keep all radar bins >= 45 dBZ 148 | 1. keep all radar bins > 30 dBZ and height > fl + 1600m 149 | 1. combine 1 and 2 150 | 1. iterate over x,y pairs and fetch from whole tree to set as convective. 151 | 152 | ```{code-cell} python 153 | def mask_data(rds, fl): 154 | if "z" in rds.coords: 155 | # Thresholding and smoothing (Park et al.) 156 | # ----------------------------------------- 157 | xwin_zh = 5 158 | rds = rds.where(rds.RH > 0.8) 159 | rds["CZ"] = rds.CZ.rolling( 160 | range=xwin_zh, min_periods=xwin_zh // 2, center=True 161 | ).mean(skipna=True) 162 | mask = (rds.CZ >= 45) | ((rds.CZ > 30) & (rds.z > (fl + 1600))) 163 | rds = rds.assign(mask=mask) 164 | return rds 165 | 166 | 167 | gr_data4 = gr_data3.map_over_datasets(mask_data, fl) 168 | ``` 169 | 170 | ## Extract xyz bin coordinates 171 | 172 | This iterates over the whole DataTree and extracts the RainType-mask as 1-dimensional array. This keeps only valid values. 173 | 174 | ```{code-cell} python 175 | def get_xyz(tree): 176 | swp_list = [] 177 | for key in list(tree.children): 178 | if "sweep" in key: 179 | ds = tree[key].ds.stack(npoints=("azimuth", "range")) 180 | ds = ds.reset_coords().where(ds.mask, drop=True) 181 | swp_list.append(ds.mask) 182 | return xr.concat(swp_list, "npoints") 183 | ``` 184 | 185 | # Interpolation of RainType mask 186 | 187 | This interpolates the RainType for all sweeps, to get a vertically consistent RainType. 188 | For this a KDTree is created containing the valid values from above, which is used for the Nearest interpolator. 189 | The ROI (maxdist) is assumed to be the current range resolution, but can be specified as keyword argument. 190 | 191 | ```{code-cell} python 192 | %%time 193 | 194 | kwargs = dict(balanced_tree=True) 195 | xyz = get_xyz(gr_data4) 196 | src = np.vstack([xyz.x.values, xyz.y.values]).T 197 | kdtree = spatial.KDTree(src, **kwargs) 198 | 199 | 200 | def get_range_res(rng): 201 | return rng.range.diff("range").median("range").values 202 | 203 | 204 | def ipol_mask(swp, xyz, kdtree, maxdist=None): 205 | if "z" in swp.coords: 206 | if maxdist is None: 207 | maxdist = swp.range.attrs.get( 208 | "meters_between_gates", get_range_res(swp.range) 209 | ) 210 | trg = np.vstack([swp.x.values.ravel(), swp.y.values.ravel()]).T 211 | nn = wrl.ipol.Nearest(kdtree, trg) 212 | out = nn(xyz.values, maxdist=maxdist).reshape(swp.x.shape) 213 | swp = swp.assign(rt=(swp.x.dims, out)) 214 | swp["rt"] = xr.where(swp["rt"] == 1, 2, 1) 215 | return swp 216 | 217 | 218 | gr_data5 = gr_data4.map_over_datasets(ipol_mask, xyz, kdtree) 219 | ``` 220 | 221 | ```{code-cell} python 222 | gr_data5["sweep_0"].rt.plot(x="x", y="y") 223 | ``` 224 | 225 | ## ZDR Offset retrieval 226 | 227 | The ZDR offset was retrieved following [A. Ryzhkov & D. Zrnic 2019, 6.2.3 Z-ZDR Consistency in Light Rain, pp. 153-156](https://doi.org/10.1007/978-3-030-05093-1). 228 | 229 | ```{code-cell} python 230 | zdr_offset = 0.5 231 | ``` 232 | 233 | ## Extract sweep 2 for further processing 234 | 235 | ```{code-cell} python 236 | swp = gr_data5["sweep_2"].ds 237 | swp 238 | ``` 239 | 240 | ```{code-cell} python 241 | fig, axs = plt.subplots(2, 2, figsize=(12, 10), sharex=True, sharey=True) 242 | swpp = swp[["CZ", "DR", "KD", "RH"]] 243 | display(swpp) 244 | 245 | LVL = [ 246 | np.arange(10, 57.5, 2.5), 247 | np.array([-1, -0.5, -0.25, -0.1, 0.1, 0.2, 0.3, 0.5, 0.75, 1, 2, 3]), 248 | np.array( 249 | [-0.5, -0.1, 0.1, 0.15, 0.2, 0.25, 0.5, 0.75, 1, 2, 3, 4] 250 | ), # np.arange(-0.5,2, 0.2), 251 | np.arange(0.9, 1.01, 0.01), 252 | ] 253 | 254 | for i, var in enumerate(swpp.data_vars.values()): 255 | cbar_kwargs = { 256 | "extend": "neither", 257 | "label": "", 258 | "pad": 0.01, 259 | "ticks": LVL[i], 260 | } 261 | ax = axs.flat[i] 262 | var.dropna("range", how="all").plot( 263 | x="x", 264 | y="y", 265 | ax=ax, 266 | cmap="HomeyerRainbow", 267 | levels=LVL[i], 268 | cbar_kwargs=cbar_kwargs, 269 | ) 270 | ax.set_title(var.attrs["long_name"]) 271 | 272 | plt.tight_layout() 273 | ``` 274 | 275 | ## Combine observations into xr.DataArray 276 | 277 | Use the mapping to bind the existing variable names to the needed names. 278 | 279 | ```{code-cell} python 280 | # mapping observations 281 | obs_mapping = { 282 | "ZH": "CZ", 283 | "ZDR": "DR", 284 | "KDP": "KD", 285 | "RHO": "RH", 286 | "RT": "rt", 287 | "TEMP": "TEMP", 288 | } 289 | polars = wrl.classify.create_gr_observations(swp, obs_mapping) 290 | polars 291 | ``` 292 | 293 | # Calculate hydrometeor partitioning ratios (HPR) 294 | 295 | This uses the loaded weights and centroids to retrieve the hydrometeor partitioning ratio from the observations. 296 | 297 | ```{code-cell} python 298 | hmpr = wrl.classify.calculate_hmpr(polars, cw.weights, cdp) 299 | ``` 300 | 301 | ## Plotting all Hydrometeor-Classes 302 | 303 | For better plotting we transfrom to 100% and drop NaN data. 304 | 305 | ```{code-cell} python 306 | hmpr = hmpr.dropna("range", how="all") * 100 307 | hpr_bins = [0, 1, 2.5, 5, 10, 15, 20, 30, 40, 50, 60, 70, 80, 90, 95, 100] 308 | 309 | hmpr.plot( 310 | col="hmc", 311 | col_wrap=3, 312 | x="x", 313 | y="y", 314 | cmap="HomeyerRainbow", 315 | levels=hpr_bins, 316 | cbar_kwargs={"ticks": hpr_bins}, 317 | ) 318 | ``` 319 | -------------------------------------------------------------------------------- /notebooks/classify/2d_hmc.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: md:myst 4 | text_representation: 5 | extension: .md 6 | format_name: myst 7 | format_version: 0.13 8 | jupytext_version: 1.18.1 9 | kernelspec: 10 | name: python3 11 | display_name: Python 3 12 | --- 13 | 14 | ```{include} ../../_includes/license_block.md 15 | ``` 16 | # Hydrometeorclassification 17 | 18 | ```{code-cell} python 19 | import datetime as dt 20 | import urllib 21 | import warnings 22 | 23 | import matplotlib as mpl 24 | import matplotlib.pyplot as plt 25 | import numpy as np 26 | import wradlib as wrl 27 | import wradlib_data 28 | import xarray as xr 29 | import xradar as xd 30 | from IPython.display import display 31 | 32 | warnings.filterwarnings("ignore") 33 | ``` 34 | 35 | The hydrometeorclassification code is based on the paper by [Zrnic et.al 2001](https://dx.doi.org/10.1175/1520-0426%282001%29018%3C0892:TAPFAC%3E2.0.CO;2) utilizing 2D trapezoidal membership functions based on the paper by [Straka et. al 2000](https://doi.org/10.1175/1520-0450(2000)039%3C1341:BHCAQU%3E2.0.CO;2) adapted by [Evaristo et. al 2013](https://ams.confex.com/ams/36Radar/webprogram/Paper229078.html) for X-Band. 36 | 37 | 38 | ## Precipitation Types 39 | 40 | ```{code-cell} python 41 | pr_types = wrl.classify.pr_types 42 | for k, v in pr_types.items(): 43 | print(str(k) + " - ".join(v)) 44 | ``` 45 | 46 | ## Membership Functions 47 | 48 | 49 | ### Load 2D Membership Functions 50 | 51 | ```{code-cell} python 52 | filename = wradlib_data.DATASETS.fetch("misc/msf_xband_v1.nc") 53 | msf = xr.open_dataset(filename) 54 | display(msf) 55 | ``` 56 | 57 | ### Plot 2D Membership Functions 58 | 59 | ```{code-cell} python 60 | minmax = [(-10, 100), (-1, 6), (0.0, 1.0), (-5, 35), (-65, 45)] 61 | 62 | for i, pr in enumerate(pr_types.values()): 63 | if pr[0] == "NP": 64 | continue 65 | fig = plt.figure(figsize=(10, 8)) 66 | t = fig.suptitle(" - ".join(pr)) 67 | t.set_y(1.02) 68 | hmc = msf.sel(hmc=pr[0]) 69 | for k, p in enumerate(hmc.data_vars.values()): 70 | p = p.where(p != 0) 71 | ax = fig.add_subplot(3, 2, k + 1) 72 | p.sel(trapezoid=0).plot(x="idp", c="k", lw=1.0, ax=ax) 73 | p.sel(trapezoid=1).plot(x="idp", c="k", lw=2.0, ax=ax) 74 | p.sel(trapezoid=2).plot(x="idp", c="k", lw=2.0, ax=ax) 75 | p.sel(trapezoid=3).plot(x="idp", c="k", lw=1.0, ax=ax) 76 | ax.set_xlim((hmc.idp.min(), hmc.idp.max())) 77 | ax.margins(x=0.05, y=0.05) 78 | t = ax.set_title(f"{p.long_name}") 79 | ax.set_ylim(minmax[k]) 80 | fig.tight_layout() 81 | plt.show() 82 | ``` 83 | 84 | ## Use Sounding Data 85 | 86 | 87 | ### Retrieve Sounding Data 88 | 89 | To get the temperature as additional discriminator we use radiosonde data from the [University of Wyoming](http://weather.uwyo.edu/upperair/sounding.html). 90 | 91 | The function {func}`wradlib.io.misc.get_radiosonde` tries to find the next next available radiosonde measurement on the given date. 92 | 93 | ```{code-cell} python 94 | rs_time = dt.datetime(2014, 6, 10, 12, 0) 95 | 96 | try: 97 | rs_data, rs_meta = wrl.io.get_radiosonde(10410, rs_time) 98 | except (urllib.error.HTTPError, urllib.error.URLError): 99 | dataf = wradlib_data.DATASETS.fetch("misc/radiosonde_10410_20140610_1200.h5") 100 | rs_data, _ = wrl.io.from_hdf5(dataf) 101 | metaf = wradlib_data.DATASETS.fetch("misc/radiosonde_10410_20140610_1200.json") 102 | with open(metaf, "r") as infile: 103 | import json 104 | 105 | rs_meta = json.load(infile) 106 | rs_meta 107 | ``` 108 | 109 | ### Extract Temperature and Height 110 | 111 | ```{code-cell} python 112 | stemp = rs_data["TEMP"] 113 | sheight = rs_data["HGHT"] 114 | # remove nans 115 | idx = np.isfinite(stemp) 116 | stemp = stemp[idx] 117 | sheight = sheight[idx] 118 | ``` 119 | 120 | ### Create DataArray 121 | 122 | ```{code-cell} python 123 | stemp_da = xr.DataArray( 124 | data=stemp, 125 | dims=["height"], 126 | coords=dict( 127 | height=(["height"], sheight), 128 | ), 129 | attrs=dict( 130 | description="Temperature.", 131 | units="degC", 132 | ), 133 | ) 134 | display(stemp_da) 135 | ``` 136 | 137 | ### Interpolate to higher resolution 138 | 139 | ```{code-cell} python 140 | hmax = 30000.0 141 | ht = np.arange(0.0, hmax) 142 | itemp_da = stemp_da.interp({"height": ht}) 143 | display(itemp_da) 144 | ``` 145 | 146 | ### Fix Temperature below first measurement 147 | 148 | ```{code-cell} python 149 | itemp_da = itemp_da.bfill(dim="height") 150 | ``` 151 | 152 | ### Plot Temperature Profile 153 | 154 | ```{code-cell} python 155 | fig = plt.figure(figsize=(5, 10)) 156 | ax = fig.add_subplot(111) 157 | itemp_da.plot(y="height", ax=ax, marker="o", zorder=0, c="r") 158 | stemp_da.to_dataset(name="stemp").plot.scatter( 159 | x="stemp", y="height", ax=ax, marker="o", c="b", zorder=1 160 | ) 161 | ax.grid(True) 162 | ``` 163 | 164 | ## Prepare Radar Data 165 | 166 | 167 | ### Load Radar Data 168 | 169 | ```{code-cell} python 170 | # read the radar volume scan 171 | filename = "hdf5/2014-06-09--185000.rhi.mvol" 172 | filename = wradlib_data.DATASETS.fetch(filename) 173 | ``` 174 | 175 | ### Extract data for georeferencing 176 | 177 | ```{code-cell} python 178 | swp = xr.open_dataset( 179 | filename, engine=xd.io.backends.GamicBackendEntrypoint, group="sweep_0", chunks={} 180 | ) 181 | swp = xd.util.remove_duplicate_rays(swp) 182 | swp = xd.util.reindex_angle( 183 | swp, start_angle=0, stop_angle=90, angle_res=0.2, direction=1 184 | ) 185 | swp 186 | ``` 187 | 188 | ```{code-cell} python 189 | swp.azimuth.load(), swp.time.load() 190 | ``` 191 | 192 | ```{code-cell} python 193 | swp.azimuth.values 194 | ``` 195 | 196 | ### Get Heights of Radar Bins 197 | 198 | ```{code-cell} python 199 | swp = swp.wrl.georef.georeference() 200 | swp.azimuth.values 201 | ``` 202 | 203 | ### Plot RHI of Heights 204 | 205 | ```{code-cell} python 206 | fig = plt.figure(figsize=(8, 7)) 207 | ax = fig.add_subplot(111) 208 | cmap = mpl.cm.viridis 209 | swp.z.plot(x="gr", y="z", ax=ax, cbar_kwargs=dict(label="Height [m]")) 210 | ax.set_xlabel("Range [m]") 211 | ax.set_ylabel("Height [m]") 212 | ax.grid(True) 213 | plt.show() 214 | ``` 215 | 216 | ### Get Index into High Res Height Array 217 | 218 | ```{code-cell} python 219 | def merge_radar_profile(rds, cds): 220 | cds = cds.interp({"height": rds.z}, method="linear") 221 | rds = rds.assign({"TEMP": cds}) 222 | return rds 223 | ``` 224 | 225 | ```{code-cell} python 226 | hmc_ds = swp.pipe(merge_radar_profile, itemp_da) 227 | display(hmc_ds) 228 | ``` 229 | 230 | ```{code-cell} python 231 | fig = plt.figure(figsize=(10, 5)) 232 | ax = fig.add_subplot(111) 233 | hmc_ds.TEMP.plot( 234 | x="gr", 235 | y="z", 236 | cmap=cmap, 237 | ax=ax, 238 | add_colorbar=True, 239 | cbar_kwargs=dict(label="Temperature [°C]"), 240 | ) 241 | ax.set_xlabel("Range [m]") 242 | ax.set_ylabel("Range [m]") 243 | ax.set_aspect("equal") 244 | ax.set_ylim(0, 30000) 245 | plt.show() 246 | ``` 247 | 248 | ## HMC Workflow 249 | 250 | 251 | ### Setup Independent Observable $Z_H$ 252 | Retrieve membership function values based on independent observable 253 | 254 | ```{code-cell} python 255 | %%time 256 | msf_val = msf.wrl.classify.msf_index_indep(swp.DBZH) 257 | display(msf_val) 258 | ``` 259 | 260 | ### Fuzzyfication 261 | 262 | ```{code-cell} python 263 | %%time 264 | fu = msf_val.wrl.classify.fuzzyfi( 265 | hmc_ds, dict(ZH="DBZH", ZDR="ZDR", RHO="RHOHV", KDP="KDP", TEMP="TEMP") 266 | ) 267 | ``` 268 | 269 | ### Probability 270 | 271 | ```{code-cell} python 272 | # weights dataset 273 | w = xr.Dataset(dict(ZH=2.0, ZDR=1.0, RHO=1.0, KDP=1.0, TEMP=1.0)) 274 | display(w) 275 | ``` 276 | 277 | ```{code-cell} python 278 | %%time 279 | prob = fu.wrl.classify.probability(w).compute() 280 | display(prob) 281 | ``` 282 | 283 | ```{code-cell} python 284 | # prob = prob.compute() 285 | ``` 286 | 287 | ### Classification 288 | 289 | ```{code-cell} python 290 | cl_res = prob.wrl.classify.classify(threshold=0.0) 291 | display(cl_res) 292 | ``` 293 | 294 | ### Compute 295 | 296 | ```{code-cell} python 297 | %%time 298 | cl_res = cl_res.compute() 299 | cl_res = cl_res.assign_coords(sweep_mode="rhi") 300 | ``` 301 | 302 | ## HMC Results 303 | 304 | 305 | ### Plot Probability of HMC Types 306 | 307 | ```{code-cell} python 308 | prob = prob.assign_coords(hmc=np.array(list(pr_types.values())).T[1][:11]) 309 | prob = prob.where(prob > 0) 310 | prob.plot(x="gr", y="z", col="hmc", col_wrap=4, cbar_kwargs=dict(label="Probability")) 311 | ``` 312 | 313 | ### Plot maximum probability 314 | 315 | ```{code-cell} python 316 | fig = plt.figure(figsize=(10, 6)) 317 | cmap = "cubehelix" 318 | im = cl_res.max("hmc").wrl.vis.plot( 319 | ax=111, 320 | crs={"angular_spacing": 20.0, "radial_spacing": 12.0, "latmin": 2.5}, 321 | cmap=cmap, 322 | fig=fig, 323 | ) 324 | cgax = plt.gca() 325 | cbar = plt.colorbar(im, ax=cgax, fraction=0.046, pad=0.05) 326 | cbar.set_label("Probability") 327 | cgax.set_xlim(0, 40000) 328 | cgax.set_ylim(0, 14000) 329 | t = cgax.set_title("Hydrometeorclassification", y=1.05) 330 | 331 | caax = cgax.parasites[0] 332 | caax.set_xlabel("Range [m]") 333 | caax.set_ylabel("Range [m]") 334 | plt.show() 335 | ``` 336 | 337 | ### Plot classification result 338 | 339 | ```{code-cell} python 340 | bounds = np.arange(-0.5, prob.shape[0] + 0.6, 1) 341 | ticks = np.arange(0, prob.shape[0] + 1) 342 | cmap = mpl.cm.get_cmap("cubehelix", len(ticks)) 343 | norm = mpl.colors.BoundaryNorm(bounds, cmap.N) 344 | ``` 345 | 346 | ```{code-cell} python 347 | hydro = cl_res.argmax("hmc") 348 | hydro.attrs = dict(long_name="Hydrometeorclassification") 349 | hydro = hydro.assign_coords(sweep_mode="rhi") 350 | ``` 351 | 352 | ```{code-cell} python 353 | fig = plt.figure(figsize=(10, 8)) 354 | im = hydro.wrl.vis.plot( 355 | ax=111, 356 | crs={"angular_spacing": 20.0, "radial_spacing": 12.0, "latmin": 2.5}, 357 | norm=norm, 358 | cmap=cmap, 359 | fig=fig, 360 | ) 361 | cgax = plt.gca() 362 | caax = cgax.parasites[0] 363 | paax = cgax.parasites[1] 364 | 365 | cbar = plt.colorbar(im, ticks=ticks, ax=cgax, fraction=0.046, norm=norm, pad=0.05) 366 | cbar.set_label("Hydrometeorclass") 367 | caax.set_xlabel("Range [km]") 368 | caax.set_ylabel("Range [km]") 369 | labels = [pr_types[i][1] for i, _ in enumerate(pr_types)] 370 | labels = cbar.ax.set_yticklabels(labels) 371 | t = cgax.set_title("Hydrometeorclassification", y=1.05) 372 | cgax.set_xlim(0, 40000) 373 | cgax.set_ylim(0, 14000) 374 | plt.tight_layout() 375 | ``` 376 | -------------------------------------------------------------------------------- /notebooks/attenuation/attenuation.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: md:myst 4 | text_representation: 5 | extension: .md 6 | format_name: myst 7 | format_version: 0.13 8 | jupytext_version: 1.18.1 9 | kernelspec: 10 | name: python3 11 | display_name: Python 3 12 | --- 13 | 14 | ```{include} ../../_includes/license_block.md 15 | ``` 16 | # Attenuation correction 17 | 18 | In this example, we will compare different approaches to **constrain the gate-by-gate retrieval** of path-integrated attenuation. 19 | 20 | ## Introduction 21 | 22 | Rainfall-induced attenuation is a major source of underestimation for radar-based precipitation estimation at C-band and X-band. Unconstrained forward gate-by-gate correction is known to be inherently unstable and thus not suited for unsupervised quality control procedures. Ideally, reference measurements (e.g. from microwave links) should be used to constrain gate-by-gate procedures. However, such attenuation references are usually not available. $\omega radlib$ provides a pragmatic approach to constrain gate-by-gate correction procedures, inspired by the work of [Kraemer et al., 2008](https://docs.wradlib.org/en/latest/bibliography.html#kraemer2008). It turned out that these procedures can effectively reduce the error introduced by attenuation, and, at the same time, minimize instability issues [(Jacobi et al., 2016)](https://docs.wradlib.org/en/latest/bibliography.html#jacobi2016). 23 | 24 | ```{code-cell} python 25 | import warnings 26 | 27 | import matplotlib.pyplot as plt 28 | import wradlib as wrl 29 | import wradlib_data 30 | 31 | warnings.filterwarnings("ignore") 32 | ``` 33 | 34 | ## The Example Event: June 2nd, 2008, SW-Germany 35 | 36 | Let's have a look at the situation in South-West Germany on June 2nd, 2008, at 16:55 UTC, as observed by the DWD C-band radar on mount Feldberg. 37 | 38 | The data can be read by the following lines and then visualized by [wradlib.vis.plot()](https://docs.wradlib.org/en/latest/generated/wradlib.vis.plot.html): 39 | 40 | ```{code-cell} python 41 | fpath = "dx/raa00-dx_10908-0806021655-fbg---bin.gz" 42 | f = wradlib_data.DATASETS.fetch(fpath) 43 | data, attrs = wrl.io.read_dx(f) 44 | ``` 45 | 46 | ```{code-cell} python 47 | plt.figure(figsize=(10, 8)) 48 | da = wrl.georef.create_xarray_dataarray(data).wrl.georef.georeference() 49 | cf = da.wrl.vis.plot(cmap="viridis") 50 | plt.xlabel("Easting from radar (km)") 51 | plt.ylabel("Northing from radar (km)") 52 | plt.title("Radar Feldberg, 2008-06-02 16:55 UTC") 53 | cb = plt.colorbar(cf, shrink=0.8) 54 | cb.set_label("dBZ") 55 | plt.plot([0, 105.6], [0, 73.4], "-", color="white", lw=2) 56 | plt.xlim(-128, 128) 57 | plt.ylim(-128, 128) 58 | plt.grid(color="grey") 59 | ``` 60 | 61 | We see a set of convective cells with high rainfall intensity in the NE-sector of the Feldberg radar. Let us examine the reflectivity profile along **three beams which at azimuths 53-55 degree** (as marked by the white line in the PPI above). 62 | 63 | ```{code-cell} python 64 | # just a little helper function 65 | 66 | 67 | def plot_beams(data, mybeams, sub=111): 68 | ax = fig.add_subplot(sub) 69 | labelsize = 13 70 | for beam in range(mybeams.start, mybeams.stop): 71 | plt.plot(data[beam], label="{0} deg".format(beam)) 72 | plt.grid() 73 | plt.text( 74 | 0.99, 75 | 0.88, 76 | "Reflectivity along beams", 77 | horizontalalignment="right", 78 | transform=ax.transAxes, 79 | fontsize="large", 80 | ) 81 | plt.xlabel("range (km)", fontsize="large") 82 | plt.ylabel("Reflectivity (dBZ)", fontsize="large") 83 | plt.legend(loc="upper left") 84 | ax.tick_params(axis="x", labelsize=labelsize) 85 | ax.tick_params(axis="y", labelsize=labelsize) 86 | plt.xlim(0, 128) 87 | ``` 88 | 89 | ```{code-cell} python 90 | mybeams = slice(53, 56) 91 | fig = plt.figure(figsize=(10, 3)) 92 | plot_beams(data, mybeams) 93 | ``` 94 | 95 | ## Examining different attenuation correction procedures 96 | 97 | ### Hitschfeld and Bordan 98 | 99 | Unconstrained gate-by-gate retrieval (1954) 100 | 101 | First, we examine the behaviour of the "classical" unconstrained forward correction which is typically referred to [Hitschfeld et al., 1954](https://docs.wradlib.org/en/latest/bibliography.html#hitschfeld1954), although Hitschfeld and Bordan themselves rejected this approach. The Path Integrated Attenuation (PIA) according to this approach can be obtained as follows: 102 | 103 | ```{code-cell} python 104 | pia_hibo = wrl.atten.correct_attenuation_hb( 105 | data, coefficients=dict(a=8.0e-5, b=0.731, gate_length=1.0), mode="warn", thrs=59.0 106 | ) 107 | ``` 108 | 109 | In the coefficients dictionary, we can pass the power law parameters of the A(Z) relation as well as the gate length (in km). If we pass "warn" as the mode argument, we will obtain a warning log in case the corrected reflectivity exceeds the value of argument ``thrs`` (dBZ). 110 | 111 | Plotting the result below the reflectivity profile, we obtain the following figure. 112 | 113 | ```{code-cell} python 114 | # another little helper function 115 | 116 | 117 | def plot_pia(pia, sub=111, title=None): 118 | ax = fig.add_subplot(sub) 119 | labelsize = 13 120 | plt.plot(pia.T) 121 | plt.grid() 122 | plt.ylim(0, 30) 123 | plt.ylabel("PIA (dB)", fontsize="large") 124 | plt.text(0.01, 0.88, title, transform=ax.transAxes, fontsize="large") 125 | ax.tick_params(axis="x", labelsize=labelsize) 126 | ax.tick_params(axis="y", labelsize=labelsize) 127 | plt.xlim(0, 128) 128 | ``` 129 | 130 | ```{code-cell} python 131 | fig = plt.figure(figsize=(10, 6)) 132 | plot_beams(data, mybeams, 211) 133 | plot_pia(pia_hibo[mybeams], 212, "PIA according to Hitchfeld and Bordan") 134 | plt.tight_layout() 135 | ``` 136 | 137 | Apparently, slight differences in the reflectivity profile can cause a dramatic change in the behaviour. While at 54 and 55 degrees, the retrieval of PIA appears to be fairly stable, the profile of PIA for 53 degree demonstrates a case of instability. 138 | 139 | ### Harrison 140 | Harrison et.al. (2000): Cap PIA at a factor of two 141 | 142 | [Harrison et al., 2000](https://docs.wradlib.org/en/latest/bibliography.html#harrison2000) suggested to simply cap PIA in case it would cause a correction of rainfall intensity by more than a factor of two. Depending on the parameters of the Z(R) relationship, that would correpond to PIA values between 4 and 5 dB (4.8 dB if we assume exponent b=1.6). 143 | 144 | One way to implement this approach would be the following: 145 | 146 | ```{code-cell} python 147 | pia_harrison = wrl.atten.correct_attenuation_hb( 148 | data, coefficients=dict(a=4.57e-5, b=0.731, gate_length=1.0), mode="warn", thrs=59.0 149 | ) 150 | pia_harrison[pia_harrison > 4.8] = 4.8 151 | ``` 152 | 153 | And the results would look like this: 154 | 155 | ```{code-cell} python 156 | fig = plt.figure(figsize=(10, 6)) 157 | 158 | mybeams = slice(53, 56) 159 | labelsize = 13 160 | 161 | plot_beams(data, mybeams, 211) 162 | plot_pia(pia_harrison[mybeams], 212, "PIA according to Harrison") 163 | plt.tight_layout() 164 | ``` 165 | 166 | ### Kraemer 167 | Kraemer et al. (2008): Iterative estimation of A(Z) relationship 168 | 169 | [Kraemer et al., 2008](https://docs.wradlib.org/en/latest/bibliography.html#kraemer2008) suggested to iteratively determine the power law parameters of the A(Z). In particular, the power law coefficient is interatively decreased until the attenuation correction does not lead to reflectivity values above a given threshold (Kraemer suggested 59 dBZ). Using $\omega radlib$, this would be called by using the function {mod}`~wradlib.atten.correct_attenuation_constrained` with a specific ``constraints`` argument: 170 | 171 | ```{code-cell} python 172 | pia_kraemer = wrl.atten.correct_attenuation_constrained( 173 | data, 174 | a_max=1.67e-4, 175 | a_min=2.33e-5, 176 | n_a=100, 177 | b_max=0.7, 178 | b_min=0.65, 179 | n_b=6, 180 | gate_length=1.0, 181 | constraints=[wrl.atten.constraint_dbz], 182 | constraint_args=[[59.0]], 183 | ) 184 | ``` 185 | 186 | In brief, this call specifies ranges of the power parameters a and b of the A(Z) relation. Beginning from the maximum values (``a_max`` and ``b_max``), the function searches for values of ``a`` and ``b`` so that the corrected reflectivity will not exceed the dBZ constraint of 59 dBZ. Compared to the previous results, the corresponding profiles of PIA look like this: 187 | 188 | ```{code-cell} python 189 | fig = plt.figure(figsize=(10, 6)) 190 | plot_beams(data, mybeams, 211) 191 | plot_pia(pia_kraemer[mybeams], 212, "PIA according to Kraemer") 192 | plt.tight_layout() 193 | ``` 194 | 195 | ### Modified Kraemer 196 | Generalised Kraemer procedure: adding additional constraints 197 | 198 | The function {mod}`~wradlib.atten.correct_attenuation_constrained` allows us to pass any kind of constraint function or lists of constraint functions via the argument ``constraints``. The arguments of these functions are passed via a nested list as argument ``constraint_args``. For example, [Jacobi et al., 2016](https://docs.wradlib.org/en/latest/bibliography.html#jacobi2016) suggested to constrain *both* the corrected reflectivity (by a maximum of 59 dBZ) *and* the resulting path-intgrated attenuation PIA (by a maximum of 20 dB): 199 | 200 | ```{code-cell} python 201 | pia_mkraemer = wrl.atten.correct_attenuation_constrained( 202 | data, 203 | a_max=1.67e-4, 204 | a_min=2.33e-5, 205 | n_a=100, 206 | b_max=0.7, 207 | b_min=0.65, 208 | n_b=6, 209 | gate_length=1.0, 210 | constraints=[wrl.atten.constraint_dbz, wrl.atten.constraint_pia], 211 | constraint_args=[[59.0], [20.0]], 212 | ) 213 | ``` 214 | 215 | ```{code-cell} python 216 | fig = plt.figure(figsize=(10, 6)) 217 | plot_beams(data, mybeams, 211) 218 | plot_pia(pia_mkraemer[mybeams], 212, "PIA according to modified Kraemer") 219 | plt.tight_layout() 220 | ``` 221 | 222 | ## Comparison of all Methods 223 | 224 | Plotting all of the above methods ([Hitschfeld and Bordan](#hitschfeld-and-bordan), [Harrison](#harrison), [Kraemer](#kraemer), [Modified Kraemer](#modified-kraemer) allows for a better comparison of their behaviour. Please refer to [Jacobi et al., 2016](https://docs.wradlib.org/en/latest/bibliography.html#jacobi2016) for an in-depth discussion of this example. 225 | 226 | ```{code-cell} python 227 | fig = plt.figure(figsize=(10, 12)) 228 | 229 | plot_beams(data, mybeams, 511) 230 | plot_pia(pia_hibo[mybeams], 512, "PIA according to Hitschfeld and Bordan") 231 | plot_pia(pia_harrison[mybeams], 513, "PIA according to Harrison") 232 | plot_pia(pia_kraemer[mybeams], 514, "PIA according to Kraemer") 233 | plot_pia(pia_mkraemer[mybeams], 515, "PIA according to modified Kraemer") 234 | plt.tight_layout() 235 | ``` 236 | --------------------------------------------------------------------------------