Maps raw cloud cover to six distinct stepped colour bands (clear green through overcast red) on a dark navy background, with fine gradation below 30% where conditions matter for astrophotography and two coarse bands above. Skips OSM base map and alpha blending entirely. The triangulation now stores raw cover values; scale_cloud_cover() is applied post-blur only in the default blending mode, keeping its behaviour identical. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
166 lines
6.2 KiB
Markdown
166 lines
6.2 KiB
Markdown
# cloud_cover
|
||
|
||
Cloud cover forecast maps for astrophotography planning, powered by DWD open data.
|
||
|
||

|
||
|
||
## What is this?
|
||
|
||
The German Weather Service (DWD) publishes free, high-resolution numerical weather
|
||
predictions through its [open-data server](https://opendata.dwd.de/). The
|
||
**ICON-D2** model covers Germany and surrounding areas with ~2 km grid spacing and
|
||
produces forecasts up to 48 hours ahead, updated every 3 hours.
|
||
|
||
This tool downloads the **total cloud cover (CLCT)** field from ICON-D2 and renders
|
||
it as a series of map images — one per forecast hour — overlaid on an OpenStreetMap
|
||
base layer. The result is a quick visual answer to "will the sky be clear tonight?"
|
||
|
||
## Quick start
|
||
|
||
```sh
|
||
cargo build --release
|
||
./target/release/cloud_cover "2026-03-07T09:00:00Z" 24
|
||
```
|
||
|
||
Output appears in `cache/2026-03-07_09UTC/` — one PNG per forecast hour plus an
|
||
animated GIF.
|
||
|
||
## Usage
|
||
|
||
```
|
||
cloud_cover [OPTIONS] <ISO_TIMESTAMP> <HOURS>
|
||
```
|
||
|
||
| Argument | Description |
|
||
|---|---|
|
||
| `ISO_TIMESTAMP` | Model run time in ISO 8601, e.g. `2026-03-07T09:00:00Z` |
|
||
| `HOURS` | Number of forecast steps to render (0–48) |
|
||
|
||
The timestamp must match an ICON-D2 model run: every 3 hours starting at 00 UTC
|
||
(00, 03, 06, 09, 12, 15, 18, 21).
|
||
|
||
### Options
|
||
|
||
| Flag | Default | Description |
|
||
|---|---|---|
|
||
| `--center-lat` | 52.56 | Map centre latitude (°N) |
|
||
| `--center-lon` | 13.08 | Map centre longitude (°E) |
|
||
| `--zoom` | 10 | OSM tile zoom level (higher = more detail, smaller area) |
|
||
| `--no-basemap` | — | Skip OSM tiles; use a plain green background |
|
||
| `--false-color` | — | False-colour mode: distinct stepped colour bands, no base map (see below) |
|
||
|
||
### False-colour mode
|
||
|
||
`--false-color` skips the OSM base layer and maps cloud cover directly to
|
||
semantically meaningful colour bands, making go/no-go decisions quick to read
|
||
at a glance — particularly useful for automated or agentic interpretation:
|
||
|
||
| Cover | Colour | Meaning |
|
||
|---|---|---|
|
||
| < 1% | Bright green | Clear sky |
|
||
| 1–5% | Light green | Near-clear |
|
||
| 5–15% | Yellow-green | Light cloud |
|
||
| 15–30% | Amber | Marginal |
|
||
| 30–60% | Orange | Cloudy |
|
||
| > 60% | Red | Overcast |
|
||
|
||
Areas outside the ICON-D2 data region are shown in dark navy.
|
||
The fine gradation below 30% reflects where cloud cover actually matters for
|
||
astrophotography; above 30% only two coarse bands are used.
|
||
|
||
### Examples
|
||
|
||
```sh
|
||
# Next 12 hours from the 18 UTC run, default viewport (Falkensee / Berlin)
|
||
./target/release/cloud_cover "2026-03-06T18:00:00Z" 12
|
||
|
||
# Wider area centred on central Germany at zoom 8
|
||
./target/release/cloud_cover "2026-03-07T06:00:00Z" 24 \
|
||
--center-lat 51.0 --center-lon 10.5 --zoom 8
|
||
|
||
# Quick run without map tiles
|
||
./target/release/cloud_cover "2026-03-07T09:00:00Z" 6 --no-basemap
|
||
|
||
# False-colour mode for easy agentic interpretation
|
||
./target/release/cloud_cover "2026-03-07T09:00:00Z" 6 --false-color
|
||
```
|
||
|
||
## Output
|
||
|
||
Each run produces files in `cache/<date>_<hour>UTC/`:
|
||
|
||
- **`clct_000001.png` … `clct_NNNNNN.png`** — one 900 × 600 px map per forecast
|
||
hour, showing cloud cover blended over the OSM base layer with a colour legend
|
||
- **`clct_animation.gif`** — animated loop of all frames (generated when there are
|
||
multiple steps)
|
||
- **`clat.grib2`, `clon.grib2`, `clct_NNN.grib2`** — cached raw forecast data;
|
||
re-running the same timestamp skips the download
|
||
|
||
Image titles show the forecast time converted to CET/CEST (Europe/Berlin). The first
|
||
frame is labelled "Conditions at …", subsequent frames "Prediction at … (+NNh)".
|
||
|
||
In the default mode, cloud cover is rendered as a continuous blend from the base map
|
||
(clear sky) toward a light blue-white tone (overcast). With `--false-color`, a stepped
|
||
colour scale is used instead (see above), with no base map.
|
||
|
||
## How it works
|
||
|
||
1. **Download** — Fetches bzip2-compressed GRIB2 files from the DWD open-data
|
||
server: two coordinate grids (`clat`, `clon`) describing the ~542,000 points of
|
||
the ICON-D2 icosahedral grid over Germany, plus one `clct` file per forecast step.
|
||
Downloads run in parallel and are cached to disk.
|
||
|
||
2. **Parse** — A built-in minimal GRIB2 decoder extracts the data arrays. Only
|
||
simple packing (data representation template 0) is implemented, which is the
|
||
format DWD uses for these fields. Grid points flagged absent by the GRIB2 bitmap
|
||
are set to NaN.
|
||
|
||
3. **Interpolate** — The irregular grid points are projected into pixel space via an
|
||
orthographic projection, then connected into a Delaunay triangulation. Each output
|
||
pixel is interpolated via barycentric coordinates within its enclosing triangle,
|
||
followed by a NaN-aware Gaussian blur to smooth triangle edges.
|
||
|
||
4. **Render** — The base layer comes from Carto Voyager OSM tiles (fetched and
|
||
cached separately for the basemap and for labels). Cloud cover is blended on top,
|
||
then map labels are composited above the clouds. City markers, a title bar, and a
|
||
colour legend complete the frame.
|
||
|
||
5. **Animate** — All frames are colour-quantised to 256-colour palettes (in parallel)
|
||
and assembled into a looping GIF.
|
||
|
||
## Dependencies
|
||
|
||
All dependencies are pure Rust or vendored C compiled into the binary — no system
|
||
shared libraries are required at runtime beyond libc.
|
||
|
||
| Crate | Role |
|
||
|---|---|
|
||
| `reqwest` (rustls-tls) | HTTP client with pure-Rust TLS |
|
||
| `bzip2` | Bzip2 decompression (vendors libbzip2) |
|
||
| `plotters` + `plotters-bitmap` | PNG rendering |
|
||
| `font8x8` | Built-in 8×8 bitmap font for map labels |
|
||
| `clap` | CLI argument parsing |
|
||
| `chrono` + `chrono-tz` | Date/time handling and timezone conversion |
|
||
| `anyhow` | Error propagation |
|
||
| `spade` | Delaunay triangulation for grid interpolation |
|
||
| `rayon` | Parallel downloads, rendering, and GIF quantisation |
|
||
| `image` | PNG decoding (loading frames for GIF assembly) |
|
||
| `gif` | GIF encoding |
|
||
|
||
## Building
|
||
|
||
```sh
|
||
cargo build --release
|
||
```
|
||
|
||
The binary is at `target/release/cloud_cover`.
|
||
|
||
## Future ideas
|
||
|
||
- Configurable display timezone (currently hardcoded to Europe/Berlin)
|
||
- Configurable city markers / observation sites
|
||
- Configurable data sources
|
||
- Vector map tile support for increased rendering resolution
|
||
- Separation of cache and output files & automatic cache management
|
||
- Automatic newest prediction fetching
|