Skip to content

Latest commit

 

History

3 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 

Repository files navigation

dmi-climate-values

Query observed climate data for Denmark from DMI's Climate Data API.

Four commands:

Command What it does
rank One place, many times — the highest or lowest N values in a series
scan Many places, one criterion — everywhere in an area crossing a threshold
stations What do nearby stations measure?
inspect What does the grid cell at this point carry?
dmi-climate-values.py <command> [options]
dmi-climate-values.py <command> --help

Built to answer things like what were the ten wettest hours near Rinkenæs and which stations in Sønderjylland have recorded a skybrud since 1990.


Requirements

pip install pandas requests pyproj

Python ≥ 3.9. pyproj is optional but recommended — see Grid cell resolution below.

No API key. DMI removed authentication from opendataapi.dmi.dk on 2 December 2025; registration and keys are no longer required for any endpoint. Calls that still send a key work fine, since the key is ignored. Fair-use rate limiting still applies.

The host moved off the old dmigw.govcloud.dk domain. Tutorials and GitHub scripts still pointing there are stale.


Quick start

# what do the nearest stations measure?
dmi-climate-values.py stations --lat 54.9007 --lon 9.5333

# what does the grid cell here carry?
dmi-climate-values.py inspect --lat 54.9007 --lon 9.5333

# ten wettest hours
dmi-climate-values.py rank --lat 54.9007 --lon 9.5333 \
    --parameter acc_precip --resolution hour

# every station within 50 km that has recorded a skybrud
dmi-climate-values.py scan --lat 54.9007 --lon 9.5333 \
    --parameter max_precip_30m --resolution day --threshold 15 --start 1990-01-01

Start with stations or inspect. Parameter availability varies by station, by grid cell, and by resolution; guessing at IDs is the main source of empty results.


rank

Pulls the full series for one place and returns the extremes.

dmi-climate-values.py rank --parameter max_temp_w_date --resolution day
dmi-climate-values.py rank --parameter min_temp --resolution day --order min
dmi-climate-values.py rank --parameter acc_precip --resolution hour --decluster 24

Station mode vs grid mode

Station mode (default) searches /collections/station/items in a box around your point, keeps stations reporting your parameter, and picks the nearest by great-circle distance. It prints the five nearest first — distance, type, operating period — so you can see what you're getting. This is what you want for extremes: a gauge measures a point, so intensity isn't diluted by area averaging.

Grid mode uses the cell containing your point. Spatially complete, no gaps where there's no station, but 10 km is still an area mean so extremes land between ERA5 and a gauge.

Declustering

Without --decluster, one storm can occupy five of ten slots as consecutive hours. --decluster 24 keeps only the peak within each 24-hour window, giving ten distinct events instead.

Usually right for precipitation. Usually wrong for temperature — a heatwave genuinely is several consecutive hot days, and suppressing them hides the duration that made it notable. Default is off; decide per parameter.


scan

Instead of one place over time, finds every place in an area crossing a value. --threshold is required.

# skybrud stations within 50 km
dmi-climate-values.py scan --lat 54.9007 --lon 9.5333 \
    --parameter max_precip_30m --resolution day --threshold 15 --start 1990-01-01

# all of Denmark, saved to CSV
dmi-climate-values.py scan --bbox 7,54,16,58 \
    --parameter max_precip_30m --resolution day --threshold 15 \
    --start 2000-01-01 --out skybrud.csv

# grid cells rather than stations
dmi-climate-values.py scan --mode grid --radius 80 \
    --parameter acc_precip --resolution day --threshold 40 --start 1990-01-01

# below a threshold
dmi-climate-values.py scan --parameter min_temp --resolution day \
    --threshold -15 --order min --radius 100

One row per place: name, coordinates, peak value, when the peak occurred, and how many times it crossed. Sorted by peak.

Extent. --radius (default 50 km) draws a box around --lat/--lon. --bbox lon_min,lat_min,lon_max,lat_max overrides it; 7,54,16,58 covers Denmark.

Volume. The API cannot filter on value, so a scan pulls everything in the extent and filters locally. It accumulates per place as it goes — peak, timestamp, count — rather than holding the series, so a national multi-decade scan stays within memory. Hourly scans chunk monthly, everything else yearly, with progress per chunk. Hourly scans over five years trigger an upfront warning: national hourly is millions of records and you want --resolution day.

Exceedance counts are not comparable across stations. A station active since 1961 shows more skybrud than one installed in 2010, and that's coverage, not climate. The count is in the output so you can normalise — the station listing carries operationFrom and operationTo if you want frequency per year rather than a tally.


Options

Shared by rank and scan

Flag Default Notes
--lat, --lon Rinkenæs WGS84 decimal degrees
--parameter acc_precip DMI parameterId
--resolution hour hour, day, month, year
--order max min for cold records and other low-end extremes
--mode station station or grid
--grid-size 10km 10km or 20km; grid mode only
--start, --end 1990 → today ISO dates
--qc-only off Keep only manually quality-controlled values
--top 10 How many rows to print
--out — CSV is written only if you pass this. Otherwise print only

rank only

Flag Default Notes
--station-id — Skip the nearest-station search
--box 0.6 Station search half-width, degrees (~65 km)
--decluster 0 Hours between events; 0 disables

scan only

Flag Default Notes
--threshold required The value to cross
--radius 50 Scan extent in km around the point
--bbox — Scan extent as lon_min,lat_min,lon_max,lat_max

stations

Flag Default Notes
--lat, --lon Rinkenæs
--box 0.6 Search half-width, degrees
--parameter — Only stations reporting this parameter
--limit 5 Stations to print

inspect

Flag Default Notes
--lat, --lon Rinkenæs
--grid-size 10km 10km or 20km

Both rank and scan print to the terminal regardless. --out results.csv additionally writes the full result set, which matters in scan where --top truncates the printed table but the CSV holds every place.


Grid cell resolution

The Danish kvadratnet is defined on ETRS89 / UTM32N (EPSG:25832), and cell IDs encode the southwest corner in units of 10 km: 10km_<northing/1e4>_<easting/1e4>. So the cell containing a point is computed, not searched for. pyproj does the projection.

This matters. An earlier version probed with a bounding box and limit=1, which returns an arbitrary cell from the box rather than the containing one — at 54.9007, 9.5333 that produced 10km_607_52, a cell spanning 9.311–9.467 lon and 54.776–54.867 lat, which doesn't contain the point at all. The correct cell is 10km_608_53.

Without pyproj the script falls back to a widening box probe that picks the nearest cell by centroid distance. Workable, but the computed route is exact.

The 20 km grid is not part of the official kvadratnet — DMI notes it only follows the same naming convention. The 20 km computation here is inferred from the format example in the docs; verify with inspect before relying on it. The 10 km computation follows the published standard.


Interpreting the output

The summary line reports count, date span, and completeness against a gap-free record. Well under 100% means gaps, relocation, or an instrumentation change — check the operating period printed above it.

Values not marked manual in qcStatus haven't been through DMI's climatologist review; these are flagged [not QC'd] by rank and * by scan. For extreme-value work use --qc-only. An unreviewed spike is as likely to be a sensor fault as a real event, and faults cluster at exactly the high end you're ranking.

Precipitation is not corrected for wind and temperature undercatch. Danish gauges typically lose 5–15% for rain, more for snow. Apply a catch correction before comparing against model output or bias-correcting.

Timestamps are UTC. Danish summer time is UTC+2, so a summer afternoon peak reads two hours earlier than local clock time. The API accepts offset-aware datetimes if you prefer local; the + must be URL-encoded as %2B.

max_precip_30m uses a different day definition from every other parameter, so its daily windows don't align with the local-day boundaries used elsewhere.


Useful parameters

Parameter Unit Resolutions on grid Note
acc_precip mm hour, day, month/year Accumulated precipitation
max_precip_30m mm day, month/year Max 30-min intensity in 24h — the skybrud metric
no_days_acc_precip_10 days day, month/year Days with ≥ 10 mm
max_temp_w_date °C hour, day, month/year Maximum temperature
min_temp °C hour, day, month/year Minimum temperature
max_wind_speed_3sec m/s hour, day, month/year Gust
mean_temp °C hour, day, month/year
drought_index — day, month/year
pot_evaporation_makkink — day, month/year

max_precip_30m is the one to reach for on cloudburst questions: DMI's skybrud definition is 15 mm in 30 minutes, and this parameter is that quantity directly. Ranking hourly acc_precip smears the peak across the hour and understates it.

Full table: https://www.dmi.dk/friedata/dokumentation/climate-data/parameters-for-10x10km-20x20km-municipality-and-country


Known limitations

No server-side ranking or value filtering. sortorder sorts by time only. Both commands therefore pull the series and filter locally. For multi-decade hourly requests that's a few hundred thousand records — fine, but not instant.

Pagination caps. Max 300,000 objects per request, max 500,000 offset. Time-chunking stays inside both. If a scan chunk hits the offset ceiling you get an explicit warning that those results are incomplete, rather than silently short numbers. For very long periods use DMI's bulk download service.

Manual gauges have no hourly data. Many Danish precipitation stations are read once daily at 08:00 and carry day only. Hourly precipitation comes from automatic and Pluvio stations. If the nearest station returns nothing at hour, check its type before assuming the query is wrong.

Parameter coverage is uneven. Not every parameter exists at every resolution, or on the grid. An empty result is more often a parameter/resolution mismatch than absent data — the inspect and stations commands tell you which.


Related sources

  • DMI Klimaatlas — projected indicators (skybrud frequency, extreme return levels) on a 1×1 km grid, per RCP scenario and time slice, via ArcGIS REST/WFS at klimaatlas-dmidata.opendata.arcgis.com. Projections, not observations — no time series to extract.
  • SVK / Spildevandskomiteen regnrækker (Skrift 30/31) — the standard for Danish stormwater dimensioning, with sub-hourly gauge intensities. The quantity DMI's 15 mm/30 min definition is defined against.
  • CERRA — 5.5 km regional reanalysis for Europe, 1984–2021. Better on convective extremes than ERA5, still not convection-permitting.
  • ERA5 — right tool for long homogeneous records, trend and circulation analysis. Wrong tool for design storms.

Licence and attribution

DMI's free data is published under CC BY. Credit DMI, link the licence, and state whether you modified the data.

References

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages