Denmark
DMI Radar Composite
How DMI's five-radar Danish composite publishes reflectivity in dBZ rather than a rate, and the Marshall-Palmer conversion and ODIM sentinels that turn one into the other.
At a glance
- Product
- Composite of 5 Danish radars (Bornholm, Rømø, Sindal, Stevns, Samsø)
- Format
- ODIM HDF5 v2.0
- Grid
- 500 m cells, max reflectivity
- Cadence
- New composite every 5 minutes
- File size
- About 50 KB per file
- Coverage
- Roughly 52-60N, 3-18E: wider than Denmark, reaching the North Sea, the Baltic and northern Germany
- Access
- STAC Features API at opendataapi.dmi.dk, no authentication
- Projection
- Stereographic, centred on 56N, 10.57E, WGS84 ellipsoid
- Units
- DBZH reflectivity (dBZ), not a rate; gain 0.5, offset -32
- Licence
- DMI Free Data programme
- Required attribution
- "Source: DMI" recommended under the DMI Free Data programme.
How it signals an absence
Three different facts, and only one of them is a zero.
On the wire these three collapse onto one field. A measurement is radarStatus: "available"; no coverage is radarStatus: "out-of-coverage"; and no measurement, a failed or empty attempt, is radarStatus: "temporarily-unavailable". That field describes the outcome of the whole routing pass, not the one network this page is about: NowcastRouter walks the registered national adapters in order and returns as soon as one reports a measurement, so a point this adapter reports as out-of-coverage can still come back available if a different adapter further down the order answers instead, and can come back temporarily-unavailable rather than out-of-coverage if any other adapter in the pass failed instead of declining. radarCoverage sits beside it: "ok" appears only together with available, and "model-fallback", meaning Open-Meteo's model nowcast answered instead, can appear with either out-of-coverage or temporarily-unavailable.
| Signal | Upstream meaning | We report | Why |
|---|---|---|---|
gdallocationinfo clean exit, empty stdout | Point off the Danish stereographic grid | out-of-coverage | Empty output on a clean exit is the out-of-grid signal itself. Permanent for the point. |
255 | ODIM nodata (sensor masked or offline) | unavailable | Inside the grid, but this sensor has nothing valid right now. Clears when the sensor returns. |
0 | ODIM undetect (measured zero) | measurement | The radar looked and decoded reflectivity to nothing. The only one of the above that may be shown as 0 mm/h. |
non-zero exit code, or an upstream failure (STAC listing, file fetch) | The read did not complete | unavailable | Kept apart from a confirmed off-grid point deliberately: a broken read is not the same fact as a permanently uncovered one. |
What the product is
DMI, the Danish Meteorological Institute, publishes a composite built from five Danish radars, Bornholm, Rømø, Sindal, Stevns and Samsø, as ODIM HDF5 v2.0 files on a 500 m grid, refreshed every 5 minutes. Each file is small, about 50 KB, and the STAC Features API that lists them at opendataapi.dmi.dk needs no authentication.
The file is projected stereographically, centred on 56N, 10.57E on the WGS84 ellipsoid. As with the UK Met Office adapter, GDAL reads that projection out of the HDF5 metadata and reprojects a WGS84 point internally, so the projection math never has to be written by hand. GDAL takes the point as lon lat, the opposite order from eccodes.
The coverage gate reaches beyond Denmark into the North Sea, the Baltic and northern Germany, which overlaps a corner of DWD's German grid. The router registers DWD before this adapter, so most of that overlap never reaches DMI at all; which of the two answers for a given point inside the overlap is a routing decision made at request time, the same point ECCC makes about its own overlap with MRMS.
Reflectivity in, rain rate out
The crucial difference from Met Office: DMI does not publish a rate. The file carries DBZH, horizontal reflectivity in dBZ, packed into a single uint8 byte per cell with gain 0.5 and offset -32. Two values in that same byte space are sentinels rather than reflectivity: 255 is nodata, meaning the contributing sensor is masked or offline, and surfaces as null; 0 is undetect, a real measurement of nothing falling, and surfaces as 0 mm/h.
Everything else goes through the Marshall-Palmer Z-R relation for stratiform rain, Z = 200 R^1.6, inverted to solve for rate: dBZ = raw x 0.5 - 32, then R = (10^(dBZ/10) / 200)^(1/1.6) mm/h. The stratiform constants, a=200 and b=1.6, are a choice made in the absence of a scene-level rain/snow classifier at the point of evaluation; DMI gives no native rate product to fall back on. A decoded byte that lands at or below 0 dBZ is also treated as a measured zero, rather than run through the power law for a fractional result that would round to nothing anyway.
Three ways to have no rate
A point read against this file answers in three different ways, the same shape as Met Office UK's ODIM file: outside the Danish stereographic grid, gdallocationinfo exits 0 with no output at all, and that emptiness on a clean exit is the out-of-grid signal, permanent for the point. Inside the grid, a byte of 255 means the sensor covering that cell is masked or offline right now, worth asking again once it returns. And a byte of 0 is a real measurement: the radar looked and reflectivity there decoded to nothing. Only that last one may be shown as 0 mm/h.
A non-zero exit code from gdallocationinfo, or a failure earlier in the pipeline (the STAC listing down, the file fetch timing out), is a fourth, separate fact: the read did not complete, which is not the same claim as a confirmed-empty cell or a confirmed-uncovered point.
Reading one point yourself
# 1. Find the freshest composite item (STAC Features, no auth).
# The API sorts newest first; a datetime filter just keeps the
# response small.
curl -s "https://opendataapi.dmi.dk/v1/radardata/collections/composite/items?limit=1&datetime=2026-08-01T00:00:00Z/.." \
| jq -r '.features[0].asset.data.href'
# 2. Download that HDF5 file, then read one point. GDAL takes the
# point as lon then lat, the opposite of eccodes' lat,lon.
gdallocationinfo -wgs84 -valonly dk.com.202608010000.500_max.h5 9.9217 57.0488
# 3. The result is a raw uint8 byte (gain 0.5, offset -32). Convert
# with the stratiform-rain Marshall-Palmer constants (a=200, b=1.6):
# dBZ = raw * 0.5 - 32
# mm/h = (10^(dBZ/10) / 200)^(1/1.6)- 255 (nodata) and 0 (undetect) are sentinels in the same uint8 space as real reflectivity, not values the Z-R formula should ever see: check for them before converting, not after.
- The datetime filter is a 1-hour rolling window, wide enough to survive minor upstream publishing lag while keeping the response small; the endpoint sorts newest first regardless.
Or call the API instead
The same reading, normalised, with the source that answered named in the response. No key and no signup for the demo endpoint.
curl "https://api.droneinfo.app/v1/demo/weather/point?lat=57.0488&lon=9.9217"