icon-uv
icon-uv calculates the UV Index from ICON weather forecasts and CAMS atmospheric composition. It provides a Python API and command-line tools for Switzerland and the surrounding area, producing:
- Hourly UV Index, clear-sky UV and direct/diffuse irradiance on the native ICON grid.
- Point forecasts using a supplied elevation, surface albedo and terrain horizon.
- Daily JSON data for town and mountain-elevation maps, including rounded values, categories, source timestamps and data-quality information.
Calculations run locally using a bundled radiation lookup table. An external radiative-transfer solver is only needed when rebuilding that table.
Installation
Install the 0.1.0 release into a Python 3.11+ environment:
pip install 'icon-uv[cams] @ https://github.com/ofuhrer/icon-uv/releases/download/v0.1.0/icon_uv-0.1.0-py3-none-any.whl'
icon-uv --help
The wheel includes the radiation table and JSON schemas. To get the example scripts and develop from source, use the repository setup below. PyPI publishing is prepared separately; see the release guide.
Python 3.11 or newer and uv are required for the following setup:
git clone https://github.com/ofuhrer/icon-uv.git
cd icon-uv
uv sync --locked --extra cams
uv run --no-sync icon-uv --help
The cams extra installs the ADS download client. Configure an ADS account and
accept the dataset terms using the CAMS API setup instructions.
ICON downloads use the public MeteoSwiss STAC service without an account.
To install into an existing Python environment, use pip install '.[cams]'
from the repository directory. Omit [cams] when working only with saved inputs.
Try a calculation without credentials
uv run --no-sync python examples/offline.py --output-dir work/offline
This small example creates synthetic ICON/CAMS inputs, saves and reopens them, and uses the real bundled radiation table to write hourly grid and point NetCDF and daily JSON. It needs no network access or credentials after installation. Its two September 2026 days illustrate the formats; they are not a weather forecast.
Calculate UV fields
Choose an available 00 UTC ICON cycle and the preceding day's 12 UTC CAMS cycle. Replace the date placeholders below; published ICON files have limited retention. This example retrieves enough hours for two Swiss local-day products.
ICON_REFERENCE="YYYY-MM-DDT00:00:00Z"
CAMS_REFERENCE="YYYY-MM-DDT12:00:00Z" # preceding calendar day
uv run --no-sync icon-uv fetch-icon \
--reference "$ICON_REFERENCE" --first-lead 1 --last-lead 48 \
--output work/icon.nc
uv run --no-sync icon-uv fetch-cams \
--reference "$CAMS_REFERENCE" --first-lead 12 --last-lead 60 \
--output work/cams.nc
uv run --no-sync icon-uv run \
--icon work/icon.nc --cams work/cams.nc --output work/uv.nc
Leads are interval boundaries in hours after initialization: ICON leads 1–48
produce 47 hourly intervals. --bbox W S E N selects a geographic subset during
download; the default is 5.3–11.2°E, 45.2–48.4°N. CAMS coverage must enclose the
ICON cells and bracket their interval midpoints. Keep downloaded files to repeat
a calculation offline.
The ICON and CAMS input filters each write normalized NetCDF, which run
consumes to compute UVI products. CAMS contains ozone in Dobson units and AOD550,
with source metadata embedded in the file.
Read the result with xarray:
import xarray as xr
with xr.open_dataset("work/uv.nc") as grid:
print(grid[["uvi", "clear_sky_uvi", "quality_flag"]])
hourly_uvi = grid.uvi.isel(cell=0).load()
uvi(member, time, cell) contains hourly means; CTRL-only output omits member.
time is the UTC interval midpoint;
time_bounds gives its start and end. Native cell IDs, coordinates, elevations
and input-source identities accompany the fields. See the
output reference for variables, units and quality flags.
Generate daily map data
The daily command exports today's and tomorrow's values for a location catalog.
The supplied issue time determines the local dates in Europe/Zurich, making
replays reproducible. Set it to your intended issuance on the ICON cycle's date:
ISSUED_AT="YYYY-MM-DDT06:00:00Z"
uv run --no-sync icon-uv daily \
--grid work/uv.nc --catalog examples/product_locations.json \
--issued-at "$ISSUED_AT" --output work/daily-uv.json
Each value is a daily maximum of a reconstructed 30-minute mean, evaluated every
five minutes. Towns use a nearby native cell; mountain entries use native cells
near 1000, 2000 or 3000 m within a region. The JSON includes raw UVI, its rounded
display value, category and ok, degraded or unavailable status.
The daily-products guide explains custom catalogs, regional aggregation, freshness and missing-data handling. It also shows how to use twelve solar samples per hour when computing the input grid.
The examples overview covers the included inputs and scripts. The MeteoSwiss map example provides 30 town locations and six mountain regions, including the Jura elevation exception, with a script that exports four days and builds an English HTML map with zoom, pan, day selection and forecast/clear-sky shading. The example page and its separate JSON data files are included; serve them with a local HTTP server as described in the guide. New forecasts use all 21 ICON members by default; daily products report their median and uncertainty. The included dated map snapshot uses CTRL.
Ensemble and CTRL forecasts
fetch-icon downloads all 21 ICON-CH2-EPS members by default. run and poi
retain the member dimension and calculate UV independently for each member.
daily reports the median of the member daily products, plus P10/P50/P90,
individual member values and uncalibrated exceedance frequencies for UVI 3, 6,
8 and 11. Regional aggregation is performed within each member first. Missing inputs are
tracked; daily values need at least 90% of the requested members (19 of 21).
fetch-icon --minimum-member-fraction 0.8 changes the default coverage threshold.
To use only the deterministic CTRL forecast, add --control when downloading:
uv run --no-sync icon-uv fetch-icon --control \
--reference "$ICON_REFERENCE" --first-lead 1 --last-lead 48 \
--output work/icon-control.nc
uv run --no-sync icon-uv run --icon work/icon-control.nc --cams work/cams.nc \
--samples 12 --output work/uv-control.nc
Use that grid with daily or poi; no further CTRL option is needed.
Python callers use fetch_icon(..., ensemble=False). Ensemble data and UV
calculations require roughly 21 times the member-dependent work of CTRL.
See ensemble products for reduction
choices, schema details and uncertainty limits.
Calculate a point forecast
Supply the point's coordinates, altitude, UV albedo and horizon:
import xarray as xr
from icon_uv.products import POI, compute_pois
site = POI(
name="my-site", latitude=46.8156, longitude=6.944, altitude_m=491,
uv_albedo=0.05,
horizon_degrees=(0.0,) * 36, # open horizon, samples every 10° from north
)
with xr.open_dataset("work/uv.nc") as grid:
points = compute_pois(grid.load(), [site])
A JSON list of the same fields can be passed to the CLI:
uv run --no-sync icon-uv poi --grid work/uv.nc \
--locations examples/davos.json --output work/davos.nc
See point forecasts for horizon conventions, spatial matching and the distinction between ambient and terrain-screened UV.
Use one location catalog for points and daily products
The shared catalog defines native points, adjusted points with explicit surface properties, and region elevation bands. Use it with both commands:
uv run --no-sync icon-uv points --grid work/uv.nc \
--locations examples/shared_locations.json --output work/points.nc
uv run --no-sync icon-uv daily --grid work/uv.nc \
--locations examples/shared_locations.json --days 3 \
--issued-at "$ISSUED_AT" --output work/daily.json
Hourly output includes point entries. Daily output also includes regions. A native
point retains model elevation and surface state; an adjusted point recalculates at
the specified elevation, albedo and coordinates. Daily values reconstruct the
30-minute peak from atmospheric/cloud state, rather than taking a maximum of
hourly point means. Existing POI, compute_pois, and --catalog inputs remain
supported. See the shared Python API for computation,
publication, screening and preflight checks.
How it works
- Recover hourly solar radiation. ICON provides surface shortwave radiation averaged since initialization. Differences between consecutive boundaries recover hourly means; pressure, albedo and snow fraction use endpoint averages.
- Add atmospheric composition. CAMS supplies total-column ozone and aerosol optical depth at 550 nm, interpolated to each cell and interval midpoint.
- Infer cloud attenuation. A radiation lookup table maps atmospheric state and cloud optical thickness to shortwave and erythemal UV irradiance. The calculation selects the effective cloud thickness matching ICON shortwave.
- Calculate UV Index. Solar geometry evolves within each hour. Erythemally weighted irradiance is integrated and multiplied by 40 to obtain UVI; daily and point products reuse the saved atmospheric and cloud state.
The table was generated with libRadtran using plane-parallel DISORT. The method description covers the physical assumptions, table ranges and interpolation. Validation results summarize measurement comparisons and numerical accuracy. Developer setup and table rebuilding are documented in CONTRIBUTING.md.
Limitations
- Cloud state is hourly; reconstructed subhourly UV follows solar geometry and does not resolve rapid cloud changes or three-dimensional cloud effects.
- Atmospheric profiles, water vapour and aerosol optical properties are fixed; UV surface albedo is estimated from snow fraction.
- Native-grid terrain can differ from a measurement or target location. Point height adjustments and horizon screening approximate local conditions.
- Low-sun accuracy depends on the plane-parallel approximation; twilight is omitted. Inputs outside the lookup table's supported ranges are rejected.
- Measurement coverage varies by site, season and weather regime; the reported validation statistics describe those sampled conditions.
Sources and attribution
- MeteoSwiss ICON documentation and STAC collection.
- CAMS atmospheric composition forecasts, produced by ECMWF for Copernicus.
- libRadtran, used to generate the radiation table.
- Example Davos terrain geometry derives from swisstopo elevation profiles.
icon-uv is licensed under the BSD 3-Clause License. Input datasets and third-party dependencies retain their own licenses and attribution requirements.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file icon_uv-0.1.0.tar.gz.
File metadata
- Download URL: icon_uv-0.1.0.tar.gz
- Upload date:
- Size: 10.4 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
78e68d75a89682c178af7270b9a583b7bc3813acee95cfaad40e10184e38f70d
|
|
| MD5 |
2b15ff16b1b658665958a520341a2418
|
|
| BLAKE2b-256 |
4cf2edfcdcf99258b3867059bb58ce42f60d0cddd4cdd927cb870773f0580fdf
|
Provenance
The following attestation bundles were made for icon_uv-0.1.0.tar.gz:
Publisher:
publish.yml on ofuhrer/icon-uv
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
icon_uv-0.1.0.tar.gz -
Subject digest:
78e68d75a89682c178af7270b9a583b7bc3813acee95cfaad40e10184e38f70d - Sigstore transparency entry: 2752755587
- Sigstore integration time:
-
Permalink:
ofuhrer/icon-uv@04638ee38174bcb09d3e975758e693ea427c8b4e -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/ofuhrer
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@04638ee38174bcb09d3e975758e693ea427c8b4e -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file icon_uv-0.1.0-py3-none-any.whl.
File metadata
- Download URL: icon_uv-0.1.0-py3-none-any.whl
- Upload date:
- Size: 268.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0961f4f97aeaa6b2b70994cea1b4d0c19f15281f22609336afbc815ee205028b
|
|
| MD5 |
36488c8a7c8b08b81882f96fe626b0e4
|
|
| BLAKE2b-256 |
e65b861faeb04a005e0e18201538e065234176a5bada3ed93e6b0b56b0fae60c
|
Provenance
The following attestation bundles were made for icon_uv-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on ofuhrer/icon-uv
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
icon_uv-0.1.0-py3-none-any.whl -
Subject digest:
0961f4f97aeaa6b2b70994cea1b4d0c19f15281f22609336afbc815ee205028b - Sigstore transparency entry: 2752755595
- Sigstore integration time:
-
Permalink:
ofuhrer/icon-uv@04638ee38174bcb09d3e975758e693ea427c8b4e -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/ofuhrer
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@04638ee38174bcb09d3e975758e693ea427c8b4e -
Trigger Event:
workflow_dispatch
-
Statement type: