Python API#
Introduction#
The API offers access to different data products. They are outlined in more detail within the data chapter. Please also check out complete examples about how to use the API in the examples folder. In order to explore all features interactively, you might want to try the cli. For managing general settings, please refer to the settings chapter.
Available APIs#
The available APIs can be accessed by the top-level API Wetterdienst. This API also allows the user to discover the available APIs of each service included:
1from wetterdienst import Wetterdienst
2
3coverage = Wetterdienst.discover()
4coverage
{'aemet': {'observation': {'auth': True,
'configured': False,
'valid': False,
'date_required': True}},
'chmi': {'observation': {'auth': False,
'configured': True,
'valid': True,
'date_required': True}},
'dmi': {'observation': {'auth': False,
'configured': True,
'valid': True,
'date_required': True}},
'dwd': {'observation': {'auth': False,
'configured': True,
'valid': True,
'date_required': False},
'mosmix': {'auth': False,
'configured': True,
'valid': True,
'date_required': False},
'dmo': {'auth': False,
'configured': True,
'valid': True,
'date_required': False},
'road': {'auth': False,
'configured': True,
'valid': True,
'date_required': True},
'swsmos': {'auth': False,
'configured': True,
'valid': True,
'date_required': False},
'poi': {'auth': False,
'configured': True,
'valid': True,
'date_required': False},
'phenology': {'auth': False,
'configured': True,
'valid': True,
'date_required': False},
'radar': {'auth': False,
'configured': True,
'valid': True,
'date_required': False},
'alerts': {'auth': False,
'configured': True,
'valid': True,
'date_required': False},
'derived': {'auth': False,
'configured': True,
'valid': True,
'date_required': False}},
'eccc': {'observation': {'auth': False,
'configured': True,
'valid': True,
'date_required': True}},
'fmi': {'observation': {'auth': False,
'configured': True,
'valid': True,
'date_required': True}},
'imgw': {'hydrology': {'auth': False,
'configured': True,
'valid': True,
'date_required': True},
'meteorology': {'auth': False,
'configured': True,
'valid': True,
'date_required': True}},
'ipma': {'observation': {'auth': False,
'configured': True,
'valid': True,
'date_required': True}},
'knmi': {'observation': {'auth': True,
'configured': False,
'valid': False,
'date_required': True}},
'lhmt': {'observation': {'auth': False,
'configured': True,
'valid': True,
'date_required': True}},
'noaa': {'ghcn': {'auth': False,
'configured': True,
'valid': True,
'date_required': True}},
'rmi': {'observation': {'auth': False,
'configured': True,
'valid': True,
'date_required': True}},
'wsv': {'pegel': {'auth': False,
'configured': True,
'valid': True,
'date_required': True}},
'ea': {'hydrology': {'auth': False,
'configured': True,
'valid': True,
'date_required': True}},
'nws': {'observation': {'auth': False,
'configured': True,
'valid': True,
'date_required': True}},
'eaufrance': {'hubeau': {'auth': False,
'configured': True,
'valid': True,
'date_required': True}},
'geosphere': {'observation': {'auth': False,
'configured': True,
'valid': True,
'date_required': True}},
'meteofrance': {'synop': {'auth': False,
'configured': True,
'valid': True,
'date_required': True},
'observation': {'auth': False,
'configured': True,
'valid': True,
'date_required': True}},
'meteoswiss': {'observation': {'auth': False,
'configured': True,
'valid': True,
'date_required': True}},
'metno': {'frost': {'auth': True,
'configured': False,
'valid': False,
'date_required': True}},
'metoffice': {'observation': {'auth': True,
'configured': False,
'valid': False,
'date_required': False}},
'smhi': {'observation': {'auth': False,
'configured': True,
'valid': True,
'date_required': False}}}
To load any of the available APIs pass the provider and the network of data to the wetterdienst api factory:
1from wetterdienst import Wetterdienst
2
3Wetterdienst(provider="dwd", network="observation")
wetterdienst.provider.dwd.observation.api.DwdObservationRequest
Request arguments#
A request is typically defined by three arguments:
parametersstartend
Parameters can be requested in different ways e.g.
using a tuple of resolution and dataset
1from wetterdienst.provider.dwd.observation import DwdObservationRequest
2
3DwdObservationRequest(
4 parameters=("daily", "climate_summary") # will resolve to all parameters of kl
5)
DwdObservationRequest(parameters=[ParameterModel(name='wind_gust_max', name_original='fx', unit='meter_per_second', description='Daily maximum of windgust.'), ParameterModel(name='wind_speed', name_original='fm', unit='meter_per_second', description='Daily mean of wind velocity.'), ParameterModel(name='precipitation_amount', name_original='rsk', unit='millimeter', description='Daily precipitation height.'), ParameterModel(name='precipitation_form', name_original='rskf', unit='dimensionless', description='Precipitation form.'), ParameterModel(name='sunshine_duration', name_original='sdk', unit='hour', description='Daily sunshine duration.'), ParameterModel(name='snow_depth', name_original='shk_tag', unit='centimeter', description='Daily snow depth.'), ParameterModel(name='cloud_cover_total', name_original='nm', unit='one_eighth', description='Daily mean of cloud cover: the arithmetic mean of at least 21 hourly values, in earlier periods the mean of the observation terms (3 terms at most stations, 4 or 8 at some).'), ParameterModel(name='pressure_vapor', name_original='vpm', unit='hectopascal', description='Daily mean of vapor pressure: the arithmetic mean of at least 21 hourly values, in earlier periods the mean of the observation terms (3 terms at most stations, 4 or 8 at some).'), ParameterModel(name='pressure_air_site', name_original='pm', unit='hectopascal', description='Daily mean of pressure: the arithmetic mean of at least 21 hourly values, in earlier periods the mean of the observation terms (3 terms at most stations, 4 or 8 at some).'), ParameterModel(name='temperature_air_mean_2m', name_original='tmk', unit='degree_celsius', description='Daily mean of temperature: the arithmetic mean of at least 21 hourly values, in earlier periods the mean of the observation terms (3 terms at most stations, TT1 to TT3: TMK=(TT1+TT2+(TT3*2))/4; 4 or 8 at some).'), ParameterModel(name='humidity_relative', name_original='upm', unit='percent', description='Daily mean of relative humidity: the arithmetic mean of at least 21 hourly values, in earlier periods the mean of the observation terms (3 terms at most stations, 4 or 8 at some).'), ParameterModel(name='temperature_air_max_2m', name_original='txk', unit='degree_celsius', description='Daily maximum of temperature at 2 m height.'), ParameterModel(name='temperature_air_min_2m', name_original='tnk', unit='degree_celsius', description='Daily minimum of temperature at 2m height.'), ParameterModel(name='temperature_air_min_0_05m', name_original='tgk', unit='degree_celsius', description='Daily minimum of air temperature at 5 cm above ground.')], start=None, end=None, settings={"cache_disable": false, "cache_dir": "/home/docs/.cache/wetterdienst", "fsspec_client_kwargs": {"headers": {"User-Agent": "wetterdienst/0.145.0 (Linux)"}, "timeout": 30}, "auth": {"aemet": null, "knmi": null, "metno_frost": null, "ceda": null}, "use_certifi": false, "read_bufr": false, "restapi_sql": false, "ts_humanize": true, "ts_shape": "long", "ts_convert_units": true, "ts_unit_targets": {}, "ts_skip_empty": false, "ts_skip_threshold": 0.95, "ts_skip_criteria": "min", "ts_drop_nulls": true, "ts_geo_station_distance_homogeneous": 40.0, "ts_geo_station_distance_heterogeneous": 20.0, "ts_geo_station_distance": {}, "ts_geo_station_distance_resolution_factors": {}, "ts_geo_use_nearby_station_distance": 1.0, "ts_geo_min_gain_of_value_pairs": 0.1, "ts_geo_num_additional_stations": 3}, periods={<Period.HISTORICAL: 'historical'>, <Period.RECENT: 'recent'>})
using a tuple of resolution, dataset, parameter
1from wetterdienst.provider.dwd.observation import DwdObservationRequest
2
3DwdObservationRequest(
4 parameters=("daily", "climate_summary", "precipitation_amount")
5)
DwdObservationRequest(parameters=[ParameterModel(name='precipitation_amount', name_original='rsk', unit='millimeter', description='Daily precipitation height.')], start=None, end=None, settings={"cache_disable": false, "cache_dir": "/home/docs/.cache/wetterdienst", "fsspec_client_kwargs": {"headers": {"User-Agent": "wetterdienst/0.145.0 (Linux)"}, "timeout": 30}, "auth": {"aemet": null, "knmi": null, "metno_frost": null, "ceda": null}, "use_certifi": false, "read_bufr": false, "restapi_sql": false, "ts_humanize": true, "ts_shape": "long", "ts_convert_units": true, "ts_unit_targets": {}, "ts_skip_empty": false, "ts_skip_threshold": 0.95, "ts_skip_criteria": "min", "ts_drop_nulls": true, "ts_geo_station_distance_homogeneous": 40.0, "ts_geo_station_distance_heterogeneous": 20.0, "ts_geo_station_distance": {}, "ts_geo_station_distance_resolution_factors": {}, "ts_geo_use_nearby_station_distance": 1.0, "ts_geo_min_gain_of_value_pairs": 0.1, "ts_geo_num_additional_stations": 3}, periods={<Period.HISTORICAL: 'historical'>, <Period.RECENT: 'recent'>})
the same with the original names
1from wetterdienst.provider.dwd.observation import DwdObservationRequest
2
3DwdObservationRequest(
4 parameters=("daily", "kl", "rsk")
5)
DwdObservationRequest(parameters=[ParameterModel(name='precipitation_amount', name_original='rsk', unit='millimeter', description='Daily precipitation height.')], start=None, end=None, settings={"cache_disable": false, "cache_dir": "/home/docs/.cache/wetterdienst", "fsspec_client_kwargs": {"headers": {"User-Agent": "wetterdienst/0.145.0 (Linux)"}, "timeout": 30}, "auth": {"aemet": null, "knmi": null, "metno_frost": null, "ceda": null}, "use_certifi": false, "read_bufr": false, "restapi_sql": false, "ts_humanize": true, "ts_shape": "long", "ts_convert_units": true, "ts_unit_targets": {}, "ts_skip_empty": false, "ts_skip_threshold": 0.95, "ts_skip_criteria": "min", "ts_drop_nulls": true, "ts_geo_station_distance_homogeneous": 40.0, "ts_geo_station_distance_heterogeneous": 20.0, "ts_geo_station_distance": {}, "ts_geo_station_distance_resolution_factors": {}, "ts_geo_use_nearby_station_distance": 1.0, "ts_geo_min_gain_of_value_pairs": 0.1, "ts_geo_num_additional_stations": 3}, periods={<Period.HISTORICAL: 'historical'>, <Period.RECENT: 'recent'>})
using the metadata model
1from wetterdienst.provider.dwd.observation import DwdObservationRequest, DwdObservationMetadata
2
3DwdObservationRequest(
4 parameters=DwdObservationMetadata.daily.kl # will resolve to all parameters of kl
5)
DwdObservationRequest(parameters=[ParameterModel(name='wind_gust_max', name_original='fx', unit='meter_per_second', description='Daily maximum of windgust.'), ParameterModel(name='wind_speed', name_original='fm', unit='meter_per_second', description='Daily mean of wind velocity.'), ParameterModel(name='precipitation_amount', name_original='rsk', unit='millimeter', description='Daily precipitation height.'), ParameterModel(name='precipitation_form', name_original='rskf', unit='dimensionless', description='Precipitation form.'), ParameterModel(name='sunshine_duration', name_original='sdk', unit='hour', description='Daily sunshine duration.'), ParameterModel(name='snow_depth', name_original='shk_tag', unit='centimeter', description='Daily snow depth.'), ParameterModel(name='cloud_cover_total', name_original='nm', unit='one_eighth', description='Daily mean of cloud cover: the arithmetic mean of at least 21 hourly values, in earlier periods the mean of the observation terms (3 terms at most stations, 4 or 8 at some).'), ParameterModel(name='pressure_vapor', name_original='vpm', unit='hectopascal', description='Daily mean of vapor pressure: the arithmetic mean of at least 21 hourly values, in earlier periods the mean of the observation terms (3 terms at most stations, 4 or 8 at some).'), ParameterModel(name='pressure_air_site', name_original='pm', unit='hectopascal', description='Daily mean of pressure: the arithmetic mean of at least 21 hourly values, in earlier periods the mean of the observation terms (3 terms at most stations, 4 or 8 at some).'), ParameterModel(name='temperature_air_mean_2m', name_original='tmk', unit='degree_celsius', description='Daily mean of temperature: the arithmetic mean of at least 21 hourly values, in earlier periods the mean of the observation terms (3 terms at most stations, TT1 to TT3: TMK=(TT1+TT2+(TT3*2))/4; 4 or 8 at some).'), ParameterModel(name='humidity_relative', name_original='upm', unit='percent', description='Daily mean of relative humidity: the arithmetic mean of at least 21 hourly values, in earlier periods the mean of the observation terms (3 terms at most stations, 4 or 8 at some).'), ParameterModel(name='temperature_air_max_2m', name_original='txk', unit='degree_celsius', description='Daily maximum of temperature at 2 m height.'), ParameterModel(name='temperature_air_min_2m', name_original='tnk', unit='degree_celsius', description='Daily minimum of temperature at 2m height.'), ParameterModel(name='temperature_air_min_0_05m', name_original='tgk', unit='degree_celsius', description='Daily minimum of air temperature at 5 cm above ground.')], start=None, end=None, settings={"cache_disable": false, "cache_dir": "/home/docs/.cache/wetterdienst", "fsspec_client_kwargs": {"headers": {"User-Agent": "wetterdienst/0.145.0 (Linux)"}, "timeout": 30}, "auth": {"aemet": null, "knmi": null, "metno_frost": null, "ceda": null}, "use_certifi": false, "read_bufr": false, "restapi_sql": false, "ts_humanize": true, "ts_shape": "long", "ts_convert_units": true, "ts_unit_targets": {}, "ts_skip_empty": false, "ts_skip_threshold": 0.95, "ts_skip_criteria": "min", "ts_drop_nulls": true, "ts_geo_station_distance_homogeneous": 40.0, "ts_geo_station_distance_heterogeneous": 20.0, "ts_geo_station_distance": {}, "ts_geo_station_distance_resolution_factors": {}, "ts_geo_use_nearby_station_distance": 1.0, "ts_geo_min_gain_of_value_pairs": 0.1, "ts_geo_num_additional_stations": 3}, periods={<Period.HISTORICAL: 'historical'>, <Period.RECENT: 'recent'>})
using a list of tuples
1from wetterdienst.provider.dwd.observation import DwdObservationRequest
2
3DwdObservationRequest(
4 parameters=[("daily", "climate_summary"), ("daily", "precipitation_more")]
5)
DwdObservationRequest(parameters=[ParameterModel(name='wind_gust_max', name_original='fx', unit='meter_per_second', description='Daily maximum of windgust.'), ParameterModel(name='wind_speed', name_original='fm', unit='meter_per_second', description='Daily mean of wind velocity.'), ParameterModel(name='precipitation_amount', name_original='rsk', unit='millimeter', description='Daily precipitation height.'), ParameterModel(name='precipitation_form', name_original='rskf', unit='dimensionless', description='Precipitation form.'), ParameterModel(name='sunshine_duration', name_original='sdk', unit='hour', description='Daily sunshine duration.'), ParameterModel(name='snow_depth', name_original='shk_tag', unit='centimeter', description='Daily snow depth.'), ParameterModel(name='cloud_cover_total', name_original='nm', unit='one_eighth', description='Daily mean of cloud cover: the arithmetic mean of at least 21 hourly values, in earlier periods the mean of the observation terms (3 terms at most stations, 4 or 8 at some).'), ParameterModel(name='pressure_vapor', name_original='vpm', unit='hectopascal', description='Daily mean of vapor pressure: the arithmetic mean of at least 21 hourly values, in earlier periods the mean of the observation terms (3 terms at most stations, 4 or 8 at some).'), ParameterModel(name='pressure_air_site', name_original='pm', unit='hectopascal', description='Daily mean of pressure: the arithmetic mean of at least 21 hourly values, in earlier periods the mean of the observation terms (3 terms at most stations, 4 or 8 at some).'), ParameterModel(name='temperature_air_mean_2m', name_original='tmk', unit='degree_celsius', description='Daily mean of temperature: the arithmetic mean of at least 21 hourly values, in earlier periods the mean of the observation terms (3 terms at most stations, TT1 to TT3: TMK=(TT1+TT2+(TT3*2))/4; 4 or 8 at some).'), ParameterModel(name='humidity_relative', name_original='upm', unit='percent', description='Daily mean of relative humidity: the arithmetic mean of at least 21 hourly values, in earlier periods the mean of the observation terms (3 terms at most stations, 4 or 8 at some).'), ParameterModel(name='temperature_air_max_2m', name_original='txk', unit='degree_celsius', description='Daily maximum of temperature at 2 m height.'), ParameterModel(name='temperature_air_min_2m', name_original='tnk', unit='degree_celsius', description='Daily minimum of temperature at 2m height.'), ParameterModel(name='temperature_air_min_0_05m', name_original='tgk', unit='degree_celsius', description='Daily minimum of air temperature at 5 cm above ground.'), ParameterModel(name='precipitation_amount', name_original='rs', unit='millimeter', description='Daily precipitation height.'), ParameterModel(name='precipitation_form', name_original='rsf', unit='dimensionless', description='Precipitation form.'), ParameterModel(name='snow_depth', name_original='sh_tag', unit='centimeter', description='Height of snow pack.'), ParameterModel(name='snow_depth_new', name_original='nsh_tag', unit='centimeter', description='Fresh snow depth.')], start=None, end=None, settings={"cache_disable": false, "cache_dir": "/home/docs/.cache/wetterdienst", "fsspec_client_kwargs": {"headers": {"User-Agent": "wetterdienst/0.145.0 (Linux)"}, "timeout": 30}, "auth": {"aemet": null, "knmi": null, "metno_frost": null, "ceda": null}, "use_certifi": false, "read_bufr": false, "restapi_sql": false, "ts_humanize": true, "ts_shape": "long", "ts_convert_units": true, "ts_unit_targets": {}, "ts_skip_empty": false, "ts_skip_threshold": 0.95, "ts_skip_criteria": "min", "ts_drop_nulls": true, "ts_geo_station_distance_homogeneous": 40.0, "ts_geo_station_distance_heterogeneous": 20.0, "ts_geo_station_distance": {}, "ts_geo_station_distance_resolution_factors": {}, "ts_geo_use_nearby_station_distance": 1.0, "ts_geo_min_gain_of_value_pairs": 0.1, "ts_geo_num_additional_stations": 3}, periods={<Period.HISTORICAL: 'historical'>, <Period.RECENT: 'recent'>})
using a list of metadata
1from wetterdienst.provider.dwd.observation import DwdObservationRequest, DwdObservationMetadata
2
3DwdObservationRequest(
4 parameters=[DwdObservationMetadata.daily.kl, DwdObservationMetadata.daily.more_precip]
5)
DwdObservationRequest(parameters=[ParameterModel(name='wind_gust_max', name_original='fx', unit='meter_per_second', description='Daily maximum of windgust.'), ParameterModel(name='wind_speed', name_original='fm', unit='meter_per_second', description='Daily mean of wind velocity.'), ParameterModel(name='precipitation_amount', name_original='rsk', unit='millimeter', description='Daily precipitation height.'), ParameterModel(name='precipitation_form', name_original='rskf', unit='dimensionless', description='Precipitation form.'), ParameterModel(name='sunshine_duration', name_original='sdk', unit='hour', description='Daily sunshine duration.'), ParameterModel(name='snow_depth', name_original='shk_tag', unit='centimeter', description='Daily snow depth.'), ParameterModel(name='cloud_cover_total', name_original='nm', unit='one_eighth', description='Daily mean of cloud cover: the arithmetic mean of at least 21 hourly values, in earlier periods the mean of the observation terms (3 terms at most stations, 4 or 8 at some).'), ParameterModel(name='pressure_vapor', name_original='vpm', unit='hectopascal', description='Daily mean of vapor pressure: the arithmetic mean of at least 21 hourly values, in earlier periods the mean of the observation terms (3 terms at most stations, 4 or 8 at some).'), ParameterModel(name='pressure_air_site', name_original='pm', unit='hectopascal', description='Daily mean of pressure: the arithmetic mean of at least 21 hourly values, in earlier periods the mean of the observation terms (3 terms at most stations, 4 or 8 at some).'), ParameterModel(name='temperature_air_mean_2m', name_original='tmk', unit='degree_celsius', description='Daily mean of temperature: the arithmetic mean of at least 21 hourly values, in earlier periods the mean of the observation terms (3 terms at most stations, TT1 to TT3: TMK=(TT1+TT2+(TT3*2))/4; 4 or 8 at some).'), ParameterModel(name='humidity_relative', name_original='upm', unit='percent', description='Daily mean of relative humidity: the arithmetic mean of at least 21 hourly values, in earlier periods the mean of the observation terms (3 terms at most stations, 4 or 8 at some).'), ParameterModel(name='temperature_air_max_2m', name_original='txk', unit='degree_celsius', description='Daily maximum of temperature at 2 m height.'), ParameterModel(name='temperature_air_min_2m', name_original='tnk', unit='degree_celsius', description='Daily minimum of temperature at 2m height.'), ParameterModel(name='temperature_air_min_0_05m', name_original='tgk', unit='degree_celsius', description='Daily minimum of air temperature at 5 cm above ground.'), ParameterModel(name='precipitation_amount', name_original='rs', unit='millimeter', description='Daily precipitation height.'), ParameterModel(name='precipitation_form', name_original='rsf', unit='dimensionless', description='Precipitation form.'), ParameterModel(name='snow_depth', name_original='sh_tag', unit='centimeter', description='Height of snow pack.'), ParameterModel(name='snow_depth_new', name_original='nsh_tag', unit='centimeter', description='Fresh snow depth.')], start=None, end=None, settings={"cache_disable": false, "cache_dir": "/home/docs/.cache/wetterdienst", "fsspec_client_kwargs": {"headers": {"User-Agent": "wetterdienst/0.145.0 (Linux)"}, "timeout": 30}, "auth": {"aemet": null, "knmi": null, "metno_frost": null, "ceda": null}, "use_certifi": false, "read_bufr": false, "restapi_sql": false, "ts_humanize": true, "ts_shape": "long", "ts_convert_units": true, "ts_unit_targets": {}, "ts_skip_empty": false, "ts_skip_threshold": 0.95, "ts_skip_criteria": "min", "ts_drop_nulls": true, "ts_geo_station_distance_homogeneous": 40.0, "ts_geo_station_distance_heterogeneous": 20.0, "ts_geo_station_distance": {}, "ts_geo_station_distance_resolution_factors": {}, "ts_geo_use_nearby_station_distance": 1.0, "ts_geo_min_gain_of_value_pairs": 0.1, "ts_geo_num_additional_stations": 3}, periods={<Period.HISTORICAL: 'historical'>, <Period.RECENT: 'recent'>})
Every request takes periods, the release the data is read from. Valid periods are
historical, recent, now and future, and which of them a dataset is published under
is part of its metadata. A dataset published under a single period has nothing to choose between,
so asking for another one is an error naming the periods it does have, rather than a request that
quietly reads all of them. periods can be given as a list or a single value.
The value can be a string, the enumeration value or the enumeration name e.g.
by using the exact enumeration e.g.
1from wetterdienst.metadata.period import Period
2
3Period.HISTORICAL
<Period.HISTORICAL: 'historical'>
by using the enumeration name or value as string e.g.
"historical" or "HISTORICAL"
The periods argument typically can be used as replacement for the start and end
arguments. In case both arguments are given they are used as a filter for the data. Left out, the
periods are derived from start/end where the provider publishes on a release
schedule that says which period holds which years – DWD observation and DWD phenology do – and
are otherwise every period the requested datasets publish.
Regarding the definition of requested parameters:
Data#
In case of the DWD, requests can be defined by either of period or
start and end. Use DwdObservationRequest.discover()
to discover available parameters based on the given filter arguments.
Stations#
all stations#
Get station information for a given dataset/parameter and period.
1import datetime as dt
2from wetterdienst.provider.dwd.observation import DwdObservationRequest
3
4request = DwdObservationRequest(
5 parameters=("daily", "precipitation_more"),
6 start=dt.datetime(2020, 1, 1),
7 end=dt.datetime(2020, 1, 20)
8)
9stations = request.all()
10df = stations.df
11df
| resolution | dataset | station_id | start_timestamp | end_timestamp | latitude | longitude | elevation | name | region |
|---|---|---|---|---|---|---|---|---|---|
| str | str | str | datetime[μs, UTC] | datetime[μs, UTC] | f64 | f64 | f64 | str | str |
| "daily" | "precipitation_more" | "00001" | 1912-01-01 00:00:00 UTC | 1986-06-30 00:00:00 UTC | 47.8413 | 8.8493 | 478.0 | "Aach" | "Baden-Württemberg" |
| "daily" | "precipitation_more" | "00002" | 1951-01-01 00:00:00 UTC | 2006-12-31 00:00:00 UTC | 50.8066 | 6.0996 | 138.0 | "Aachen (Kläranlage)" | "Nordrhein-Westfalen" |
| "daily" | "precipitation_more" | "00003" | 1891-01-01 00:00:00 UTC | 2011-03-31 00:00:00 UTC | 50.7827 | 6.0941 | 202.0 | "Aachen" | "Nordrhein-Westfalen" |
| "daily" | "precipitation_more" | "00004" | 1951-01-01 00:00:00 UTC | 1979-10-31 00:00:00 UTC | 50.7683 | 6.1207 | 243.0 | "Aachen-Brand" | "Nordrhein-Westfalen" |
| "daily" | "precipitation_more" | "00006" | 1982-11-01 00:00:00 UTC | 2026-10-10 00:00:00 UTC | 48.8361 | 10.0598 | 455.0 | "Aalen-Unterrombach" | "Baden-Württemberg" |
| … | … | … | … | … | … | … | … | … | … |
| "daily" | "precipitation_more" | "20107" | 2025-03-27 00:00:00 UTC | 2026-10-10 00:00:00 UTC | 49.9687 | 6.8272 | 311.0 | "Wittlich-Bergweiler" | "Rheinland-Pfalz" |
| "daily" | "precipitation_more" | "20108" | 2025-05-01 00:00:00 UTC | 2026-10-10 00:00:00 UTC | 51.1543 | 6.5049 | 56.0 | "Mönchengladbach-Schelsen" | "Nordrhein-Westfalen" |
| "daily" | "precipitation_more" | "20110" | 1969-01-01 00:00:00 UTC | 2005-07-31 00:00:00 UTC | 52.3183 | 11.5677 | 64.0 | "Colbitz" | "Sachsen-Anhalt" |
| "daily" | "precipitation_more" | "20189" | 2025-10-01 00:00:00 UTC | 2026-04-30 00:00:00 UTC | 47.6759 | 12.47 | 687.0 | "Reit im Winkl (Kaiserweg)" | "Bayern" |
| "daily" | "precipitation_more" | "20191" | 2025-09-01 00:00:00 UTC | 2026-10-10 00:00:00 UTC | 50.9238 | 14.357 | 375.0 | "Sebnitz-Hinterhermsdorf" | "Sachsen" |
The function returns a Polars DataFrame with information about the available stations.
filter by station id#
1import datetime as dt
2from wetterdienst.provider.dwd.observation import DwdObservationRequest
3
4request = DwdObservationRequest(
5 parameters=("daily", "precipitation_more"),
6 start=dt.datetime(2020, 1, 1),
7 end=dt.datetime(2020, 1, 20)
8)
9stations = request.filter_by_station_id(station_id=("01048", ))
10df = stations.df
11df
| resolution | dataset | station_id | start_timestamp | end_timestamp | latitude | longitude | elevation | name | region |
|---|---|---|---|---|---|---|---|---|---|
| str | str | str | datetime[μs, UTC] | datetime[μs, UTC] | f64 | f64 | f64 | str | str |
| "daily" | "precipitation_more" | "01048" | 1926-04-25 00:00:00 UTC | 2026-10-10 00:00:00 UTC | 51.1278 | 13.7543 | 228.0 | "Dresden-Klotzsche" | "Sachsen" |
filter by name#
Station name filtering uses fuzzy matching (case-insensitive, via rapidfuzz’s WRatio
scorer) so partial names, minor typos, and word-order variations are handled
automatically. In particular a bare place name resolves to its stations, e.g.
name="Kiel" matches Kiel-Holtenau and Kiel-Kronshagen.
1import datetime as dt
2from wetterdienst.provider.dwd.observation import DwdObservationRequest
3
4request = DwdObservationRequest(
5 parameters=("daily", "precipitation_more"),
6 start=dt.datetime(2020, 1, 1),
7 end=dt.datetime(2020, 1, 20)
8)
9stations = request.filter_by_name(name="Dresden-Klotzsche")
10df = stations.df
11df
| resolution | dataset | station_id | start_timestamp | end_timestamp | latitude | longitude | elevation | name | region |
|---|---|---|---|---|---|---|---|---|---|
| str | str | str | datetime[μs, UTC] | datetime[μs, UTC] | f64 | f64 | f64 | str | str |
| "daily" | "precipitation_more" | "01048" | 1926-04-25 00:00:00 UTC | 2026-10-10 00:00:00 UTC | 51.1278 | 13.7543 | 228.0 | "Dresden-Klotzsche" | "Sachsen" |
The threshold parameter (0–1, default 0.8) controls how strictly the name must
match. Lower values accept more typos; higher values require a closer match. The
rank parameter limits how many stations are returned (default 1).
Because WRatio is a partial matcher, a query that is a common sub-token matches many
stations (e.g. name="Bad" matches every “…, Bad” station). For an exact match you have two
options: set threshold=1.0, which keeps only matches scoring 100 (so name="Kiel" returns
nothing while name="Aach" returns just Aach); or, for strict string equality, use
filter_by_sql, e.g. filter_by_sql("name = 'Aach'").
1import datetime as dt
2from wetterdienst.provider.dwd.observation import DwdObservationRequest
3
4request = DwdObservationRequest(
5 parameters=("daily", "precipitation_more"),
6 start=dt.datetime(2020, 1, 1),
7 end=dt.datetime(2020, 1, 20)
8)
9# case-insensitive, typo-tolerant, returns up to 3 matches
10stations = request.filter_by_name(name="dresden", rank=3, threshold=0.8)
11df = stations.df
12df
| resolution | dataset | station_id | start_timestamp | end_timestamp | latitude | longitude | elevation | name | region |
|---|---|---|---|---|---|---|---|---|---|
| str | str | str | datetime[μs, UTC] | datetime[μs, UTC] | f64 | f64 | f64 | str | str |
| "daily" | "precipitation_more" | "01047" | 1828-01-01 00:00:00 UTC | 1992-11-30 00:00:00 UTC | 51.0557 | 13.7274 | 112.0 | "Dresden (Mitte)" | "Sachsen" |
| "daily" | "precipitation_more" | "01048" | 1926-04-25 00:00:00 UTC | 2026-10-10 00:00:00 UTC | 51.1278 | 13.7543 | 228.0 | "Dresden-Klotzsche" | "Sachsen" |
| "daily" | "precipitation_more" | "01049" | 1995-08-01 00:00:00 UTC | 2004-12-31 00:00:00 UTC | 51.0582 | 13.6694 | 147.0 | "Dresden-Leutewitz" | "Sachsen" |
filter by distance#
Distance in kilometers (default)
1import datetime as dt
2from wetterdienst.provider.dwd.observation import DwdObservationRequest
3
4hamburg = (53.551086, 9.993682)
5request = DwdObservationRequest(
6 parameters=("hourly", "temperature_air"),
7 start=dt.datetime(2020, 1, 1),
8 end=dt.datetime(2020, 1, 20)
9)
10stations = request.filter_by_distance(latlon=hamburg, distance=30, unit="km")
11df = stations.df
12df
| resolution | dataset | station_id | start_timestamp | end_timestamp | latitude | longitude | elevation | name | region | distance |
|---|---|---|---|---|---|---|---|---|---|---|
| str | str | str | datetime[μs, UTC] | datetime[μs, UTC] | f64 | f64 | f64 | str | str | f64 |
| "hourly" | "temperature_air" | "01975" | 1949-01-01 00:00:00 UTC | 2026-10-10 00:00:00 UTC | 53.6332 | 9.9881 | 11.0 | "Hamburg-Fuhlsbüttel" | "Hamburg" | 9.1381 |
| "hourly" | "temperature_air" | "01981" | 2005-03-01 00:00:00 UTC | 2026-10-10 00:00:00 UTC | 53.4776 | 9.8957 | 4.0 | "Hamburg-Neuwiedenthal" | "Hamburg" | 10.4279 |
| "hourly" | "temperature_air" | "00052" | 1976-01-01 00:00:00 UTC | 1988-01-01 00:00:00 UTC | 53.6623 | 10.199 | 46.0 | "Ahrensburg-Wulfsdorf" | "Schleswig-Holstein" | 18.3417 |
| "hourly" | "temperature_air" | "00760" | 2017-12-01 00:00:00 UTC | 2026-10-10 00:00:00 UTC | 53.3629 | 9.9435 | 83.0 | "Rosengarten-Klecken" | "Niedersachsen" | 21.1875 |
| "hourly" | "temperature_air" | "04039" | 1988-01-11 00:00:00 UTC | 2026-10-10 00:00:00 UTC | 53.7331 | 9.8776 | 11.0 | "Quickborn" | "Schleswig-Holstein" | 21.6373 |
| "hourly" | "temperature_air" | "04857" | 2004-09-01 00:00:00 UTC | 2026-10-10 00:00:00 UTC | 53.5534 | 9.6097 | 2.0 | "Mittelnkirchen-Hohenfelde" | "Niedersachsen" | 25.367 |
Distance in miles
1import datetime as dt
2from wetterdienst.provider.dwd.observation import DwdObservationRequest
3
4hamburg = (53.551086, 9.993682)
5request = DwdObservationRequest(
6 parameters=("hourly", "temperature_air"),
7 start=dt.datetime(2020, 1, 1),
8 end=dt.datetime(2020, 1, 20)
9)
10stations = request.filter_by_distance(latlon=hamburg, distance=30, unit="mi")
11df = stations.df
12df
| resolution | dataset | station_id | start_timestamp | end_timestamp | latitude | longitude | elevation | name | region | distance |
|---|---|---|---|---|---|---|---|---|---|---|
| str | str | str | datetime[μs, UTC] | datetime[μs, UTC] | f64 | f64 | f64 | str | str | f64 |
| "hourly" | "temperature_air" | "01975" | 1949-01-01 00:00:00 UTC | 2026-10-10 00:00:00 UTC | 53.6332 | 9.9881 | 11.0 | "Hamburg-Fuhlsbüttel" | "Hamburg" | 9.1381 |
| "hourly" | "temperature_air" | "01981" | 2005-03-01 00:00:00 UTC | 2026-10-10 00:00:00 UTC | 53.4776 | 9.8957 | 4.0 | "Hamburg-Neuwiedenthal" | "Hamburg" | 10.4279 |
| "hourly" | "temperature_air" | "00052" | 1976-01-01 00:00:00 UTC | 1988-01-01 00:00:00 UTC | 53.6623 | 10.199 | 46.0 | "Ahrensburg-Wulfsdorf" | "Schleswig-Holstein" | 18.3417 |
| "hourly" | "temperature_air" | "00760" | 2017-12-01 00:00:00 UTC | 2026-10-10 00:00:00 UTC | 53.3629 | 9.9435 | 83.0 | "Rosengarten-Klecken" | "Niedersachsen" | 21.1875 |
| "hourly" | "temperature_air" | "04039" | 1988-01-11 00:00:00 UTC | 2026-10-10 00:00:00 UTC | 53.7331 | 9.8776 | 11.0 | "Quickborn" | "Schleswig-Holstein" | 21.6373 |
| "hourly" | "temperature_air" | "04857" | 2004-09-01 00:00:00 UTC | 2026-10-10 00:00:00 UTC | 53.5534 | 9.6097 | 2.0 | "Mittelnkirchen-Hohenfelde" | "Niedersachsen" | 25.367 |
| "hourly" | "temperature_air" | "05280" | 2007-03-01 00:00:00 UTC | 2026-10-10 00:00:00 UTC | 53.9224 | 10.2267 | 33.0 | "Wittenborn" | "Schleswig-Holstein" | 44.0408 |
| "hourly" | "temperature_air" | "01736" | 2002-01-24 00:00:00 UTC | 2026-10-10 00:00:00 UTC | 53.5731 | 10.6797 | 26.0 | "Grambek" | "Schleswig-Holstein" | 45.3735 |
filter by rank#
1import datetime as dt
2from wetterdienst.provider.dwd.observation import DwdObservationRequest
3
4hamburg = (53.551086, 9.993682)
5request = DwdObservationRequest(
6 parameters=("hourly", "temperature_air"),
7 start=dt.datetime(2020, 1, 1),
8 end=dt.datetime(2020, 1, 20)
9)
10stations = request.filter_by_rank(latlon=hamburg, rank=5)
11df = stations.df
12df
| resolution | dataset | station_id | start_timestamp | end_timestamp | latitude | longitude | elevation | name | region | distance |
|---|---|---|---|---|---|---|---|---|---|---|
| str | str | str | datetime[μs, UTC] | datetime[μs, UTC] | f64 | f64 | f64 | str | str | f64 |
| "hourly" | "temperature_air" | "01975" | 1949-01-01 00:00:00 UTC | 2026-10-10 00:00:00 UTC | 53.6332 | 9.9881 | 11.0 | "Hamburg-Fuhlsbüttel" | "Hamburg" | 9.1381 |
| "hourly" | "temperature_air" | "01981" | 2005-03-01 00:00:00 UTC | 2026-10-10 00:00:00 UTC | 53.4776 | 9.8957 | 4.0 | "Hamburg-Neuwiedenthal" | "Hamburg" | 10.4279 |
| "hourly" | "temperature_air" | "00052" | 1976-01-01 00:00:00 UTC | 1988-01-01 00:00:00 UTC | 53.6623 | 10.199 | 46.0 | "Ahrensburg-Wulfsdorf" | "Schleswig-Holstein" | 18.3417 |
| "hourly" | "temperature_air" | "00760" | 2017-12-01 00:00:00 UTC | 2026-10-10 00:00:00 UTC | 53.3629 | 9.9435 | 83.0 | "Rosengarten-Klecken" | "Niedersachsen" | 21.1875 |
| "hourly" | "temperature_air" | "04039" | 1988-01-11 00:00:00 UTC | 2026-10-10 00:00:00 UTC | 53.7331 | 9.8776 | 11.0 | "Quickborn" | "Schleswig-Holstein" | 21.6373 |
| … | … | … | … | … | … | … | … | … | … | … |
| "hourly" | "temperature_air" | "03730" | 1948-01-01 00:00:00 UTC | 2026-10-10 00:00:00 UTC | 47.3984 | 10.2759 | 806.0 | "Oberstdorf" | "Bayern" | 684.4374 |
| "hourly" | "temperature_air" | "05792" | 1950-01-01 00:00:00 UTC | 2026-10-10 00:00:00 UTC | 47.421 | 10.9848 | 2956.0 | "Zugspitze" | "Bayern" | 685.2131 |
| "hourly" | "temperature_air" | "07325" | 2011-09-01 00:00:00 UTC | 2015-12-31 00:00:00 UTC | 47.4165 | 10.9795 | 2650.0 | "Schneefernerhaus" | "Bayern" | 685.6732 |
| "hourly" | "temperature_air" | "00361" | 1948-01-01 00:00:00 UTC | 1976-01-01 00:00:00 UTC | 47.6344 | 13.0109 | 550.0 | "Berchtesgaden (KKst)" | "Bayern" | 691.3582 |
| "hourly" | "temperature_air" | "19856" | 2024-08-01 00:00:00 UTC | 2026-10-10 00:00:00 UTC | 47.6134 | 12.9819 | 625.0 | "Schönau am Königssee" | "Bayern" | 692.9712 |
Note
Unlike filter_by_distance, filter_by_rank returns all stations sorted by
distance in stations.df, not just rank rows. The rank limit is applied lazily
during value collection: Wetterdienst walks the distance-sorted stations and stops
once rank stations with data have been consumed. To see the rank closest
stations that actually returned data, use stations.values.all().df_stations rather
than stations.df. Enable ts_skip_empty to walk past a station that returned data
but too little of it, as ts_skip_threshold and ts_skip_criteria define.
filter by bbox#
1import datetime as dt
2from wetterdienst.provider.dwd.observation import DwdObservationRequest
3
4bbox = (8.9, 50.0, 8.91, 50.01)
5request = DwdObservationRequest(
6 parameters=("hourly", "temperature_air"),
7 start=dt.datetime(2020, 1, 1),
8 end=dt.datetime(2020, 1, 20)
9)
10stations = request.filter_by_bbox(*bbox)
11df = stations.df
12df
| resolution | dataset | station_id | start_timestamp | end_timestamp | latitude | longitude | elevation | name | region |
|---|---|---|---|---|---|---|---|---|---|
| str | str | str | datetime[μs, UTC] | datetime[μs, UTC] | f64 | f64 | f64 | str | str |
Values#
Values are just an extension of requests. You can query data by using the
.query() method on the values object:
1from wetterdienst.provider.dwd.observation import DwdObservationRequest
2from wetterdienst import Settings
3
4# if no settings are provided, default settings are used which are
5# Settings(ts_shape="long", ts_humanize=True, ts_convert_units=True)
6request = DwdObservationRequest(
7 parameters=[("daily", "kl"), ("daily", "solar")],
8 start="1990-01-01",
9 end="2020-01-01",
10)
11stations = request.filter_by_station_id(station_id=("00003", "01048"))
12
13# From here you can query data by station
14for result in stations.values.query():
15 # analyse the station here
16 break
17
18df = result.df.drop_nulls()
19df
| station_id | resolution | dataset | parameter | timestamp | value | quality |
|---|---|---|---|---|---|---|
| str | str | str | str | datetime[μs, UTC] | f64 | f64 |
| "00003" | "daily" | "climate_summary" | "cloud_cover_total" | 1990-01-01 00:00:00 UTC | 1.0 | 10.0 |
| "00003" | "daily" | "climate_summary" | "cloud_cover_total" | 1990-01-02 00:00:00 UTC | 1.0 | 10.0 |
| "00003" | "daily" | "climate_summary" | "cloud_cover_total" | 1990-01-03 00:00:00 UTC | 0.5875 | 10.0 |
| "00003" | "daily" | "climate_summary" | "cloud_cover_total" | 1990-01-04 00:00:00 UTC | 0.75 | 10.0 |
| "00003" | "daily" | "climate_summary" | "cloud_cover_total" | 1990-01-05 00:00:00 UTC | 0.9625 | 10.0 |
| … | … | … | … | … | … | … |
| "00003" | "daily" | "climate_summary" | "wind_speed" | 2011-03-27 00:00:00 UTC | 1.7 | 10.0 |
| "00003" | "daily" | "climate_summary" | "wind_speed" | 2011-03-28 00:00:00 UTC | 1.7 | 10.0 |
| "00003" | "daily" | "climate_summary" | "wind_speed" | 2011-03-29 00:00:00 UTC | 1.6 | 10.0 |
| "00003" | "daily" | "climate_summary" | "wind_speed" | 2011-03-30 00:00:00 UTC | 3.8 | 10.0 |
| "00003" | "daily" | "climate_summary" | "wind_speed" | 2011-03-31 00:00:00 UTC | 7.0 | 10.0 |
Or you can query all data at once:
1from wetterdienst.provider.dwd.observation import DwdObservationRequest
2from wetterdienst import Settings
3
4# if no settings are provided, default settings are used which are
5# Settings(ts_shape="long", ts_humanize=True, ts_convert_units=True)
6request = DwdObservationRequest(
7 parameters=[("daily", "kl"), ("daily", "solar")],
8 start="1990-01-01",
9 end="2020-01-01",
10)
11stations = request.filter_by_station_id(station_id=("00003", "01048"))
12df = stations.values.all().df.drop_nulls()
13df
| station_id | resolution | dataset | parameter | timestamp | value | quality |
|---|---|---|---|---|---|---|
| enum | enum | enum | enum | datetime[μs, UTC] | f64 | f64 |
| "00003" | "daily" | "climate_summary" | "cloud_cover_total" | 1990-01-01 00:00:00 UTC | 1.0 | 10.0 |
| "00003" | "daily" | "climate_summary" | "cloud_cover_total" | 1990-01-02 00:00:00 UTC | 1.0 | 10.0 |
| "00003" | "daily" | "climate_summary" | "cloud_cover_total" | 1990-01-03 00:00:00 UTC | 0.5875 | 10.0 |
| "00003" | "daily" | "climate_summary" | "cloud_cover_total" | 1990-01-04 00:00:00 UTC | 0.75 | 10.0 |
| "00003" | "daily" | "climate_summary" | "cloud_cover_total" | 1990-01-05 00:00:00 UTC | 0.9625 | 10.0 |
| … | … | … | … | … | … | … |
| "01048" | "daily" | "solar" | "sunshine_duration" | 2019-12-28 00:00:00 UTC | 0.0 | 1.0 |
| "01048" | "daily" | "solar" | "sunshine_duration" | 2019-12-29 00:00:00 UTC | 22320.0 | 1.0 |
| "01048" | "daily" | "solar" | "sunshine_duration" | 2019-12-30 00:00:00 UTC | 5400.0 | 1.0 |
| "01048" | "daily" | "solar" | "sunshine_duration" | 2019-12-31 00:00:00 UTC | 3960.0 | 1.0 |
| "01048" | "daily" | "solar" | "sunshine_duration" | 2020-01-01 00:00:00 UTC | 14040.0 | 1.0 |
This gives us the most options to work with the data, getting multiple parameters at
once, parsed nicely into column structure with improved parameter names. Instead of
start and end you may as well want to use periods to update your
database once in a while with a fixed set of records.
A result can be cut down after the fact with values.filter_by_date("2020-08"), which takes the
same date strings as the CLI and the REST API. Each covers everything it names: "2020-08-01"
is that whole day – all 24 readings of it for hourly data – "2020-08" the month and
"2020" the year, while an interval such as "2017-01/2019-12" runs from the start of the
first span to the end of the second. A date carrying a time, "2020-08-01T12", names one
instant and is matched exactly.
The metadata columns station_id, resolution, dataset and parameter of the
DataFrame returned by .values.all() are stored as polars
Enum
instead of String to reduce the memory footprint (these columns repeat on every row and
Enum stores them as small integer codes). Enum behaves like String for most grouping,
sorting and == comparisons, but it has a few sharp edges: it does not support the .str
namespace, it compares unequal to a String column (so joins need matching dtypes), and
is_in([...]) raises if any of the given values is not one of the column’s categories
(e.g. filtering for a parameter that is absent from the result) instead of returning an empty
selection. If you need plain String columns again — e.g. for .str operations,
is_in with possibly-absent values, strict dtype checks or when concatenating with a
String DataFrame — cast them back:
import polars as pl
# cast every Enum column back to String
df = df.with_columns(pl.col(pl.Enum).cast(pl.String))
This matters in particular for station_id: polars raises a SchemaError when a join
key is Enum on one side and String on the other. The stations DataFrame
(request.df) keeps station_id as String, so joining it — or any other String
station table — with the values DataFrame requires casting the join key first:
import polars as pl
# join values (Enum station_id) with a String station table
merged = df.with_columns(pl.col("station_id").cast(pl.String)).join(stations, on="station_id")
It also affects filtering by name with is_in. There is no “soft”/non-raising flag on
is_in — if any queried value is not a category of the Enum column it raises instead of
returning an empty selection. Cast the column to String for the comparison (an == check
against a single value, on the other hand, does handle absent values gracefully):
import polars as pl
# filter by parameters that may or may not be present in the result
subset = df.filter(pl.col("parameter").cast(pl.String).is_in(["temperature_air_mean_2m", "sunshine_duration"]))
In case you use filter_by_rank you may want to skip empty stations. We can use the Settings from
settings to achieve that:
1from wetterdienst import Settings
2from wetterdienst.provider.dwd.observation import DwdObservationRequest
3
4settings = Settings(ts_skip_empty=True, ts_skip_criteria="min", ts_skip_threshold=0.2)
5karlsruhe = (49.19780976647141, 8.135207205143768)
6request = DwdObservationRequest(
7 parameters=[("daily", "kl")],
8 start="2021-01-01",
9 end="2021-12-31",
10 settings=settings,
11)
12stations = request.filter_by_rank(latlon=karlsruhe, rank=2)
13values = stations.values.all()
14print(values.df.head())
15# df_stations has only stations that appear in the values
16values.df_stations
shape: (5, 7)
┌────────────┬────────────┬─────────────────┬─────────────────┬─────────────────┬────────┬─────────┐
│ station_id ┆ resolution ┆ dataset ┆ parameter ┆ timestamp ┆ value ┆ quality │
│ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │
│ enum ┆ enum ┆ enum ┆ enum ┆ datetime[μs, ┆ f64 ┆ f64 │
│ ┆ ┆ ┆ ┆ UTC] ┆ ┆ │
╞════════════╪════════════╪═════════════════╪═════════════════╪═════════════════╪════════╪═════════╡
│ 05426 ┆ daily ┆ climate_summary ┆ cloud_cover_tot ┆ 2021-01-01 ┆ 0.9625 ┆ 9.0 │
│ ┆ ┆ ┆ al ┆ 00:00:00 UTC ┆ ┆ │
│ 05426 ┆ daily ┆ climate_summary ┆ cloud_cover_tot ┆ 2021-01-02 ┆ 0.9875 ┆ 9.0 │
│ ┆ ┆ ┆ al ┆ 00:00:00 UTC ┆ ┆ │
│ 05426 ┆ daily ┆ climate_summary ┆ cloud_cover_tot ┆ 2021-01-03 ┆ 0.975 ┆ 9.0 │
│ ┆ ┆ ┆ al ┆ 00:00:00 UTC ┆ ┆ │
│ 05426 ┆ daily ┆ climate_summary ┆ cloud_cover_tot ┆ 2021-01-04 ┆ 1.0 ┆ 9.0 │
│ ┆ ┆ ┆ al ┆ 00:00:00 UTC ┆ ┆ │
│ 05426 ┆ daily ┆ climate_summary ┆ cloud_cover_tot ┆ 2021-01-05 ┆ 1.0 ┆ 9.0 │
│ ┆ ┆ ┆ al ┆ 00:00:00 UTC ┆ ┆ │
└────────────┴────────────┴─────────────────┴─────────────────┴─────────────────┴────────┴─────────┘
| resolution | dataset | station_id | start_timestamp | end_timestamp | latitude | longitude | elevation | name | region | distance |
|---|---|---|---|---|---|---|---|---|---|---|
| str | str | str | datetime[μs, UTC] | datetime[μs, UTC] | f64 | f64 | f64 | str | str | f64 |
| "daily" | "climate_summary" | "05426" | 1953-01-01 00:00:00 UTC | 2026-10-10 00:00:00 UTC | 49.3758 | 8.1212 | 553.0 | "Weinbiet" | "Rheinland-Pfalz" | 19.8177 |
| "daily" | "climate_summary" | "04177" | 1948-01-01 00:00:00 UTC | 2026-10-10 00:00:00 UTC | 48.9726 | 8.3301 | 116.0 | "Rheinstetten" | "Baden-Württemberg" | 28.7846 |
Interpolation & Summary#
Wetterdienst can derive a time series for an arbitrary location from the surrounding station network, either by spatial interpolation or by combining the nearest available stations. See the dedicated Interpolation & Summary chapter for the full explanation, supported parameters, settings, CLI and REST usage.
Format#
To Dict#
1from wetterdienst.provider.dwd.observation import DwdObservationRequest
2
3request = DwdObservationRequest(
4 parameters=("daily", "kl", "temperature_air_mean_2m"),
5 start="2020-01-01",
6 end="2020-01-02"
7)
8stations = request.filter_by_station_id(station_id="01048")
9values = stations.values.all()
10values.to_dict(with_metadata=True, with_stations=True)
{'metadata': {'provider': {'name_local': 'Deutscher Wetterdienst',
'name_english': 'German Weather Service',
'country': 'Germany',
'copyright': '© Deutscher Wetterdienst (DWD), Climate Data Center (CDC)',
'url': 'https://opendata.dwd.de/climate_environment/CDC/'},
'producer': {'name': 'wetterdienst',
'version': '0.145.0',
'repository': 'https://github.com/earthobservations/wetterdienst',
'documentation': 'https://wetterdienst.readthedocs.io',
'doi': '10.5281/zenodo.3960624'}},
'stations': [{'resolution': 'daily',
'dataset': 'climate_summary',
'station_id': '01048',
'start_timestamp': '1934-01-01T00:00:00.000000+00:00',
'end_timestamp': '2026-10-10T00:00:00.000000+00:00',
'latitude': 51.1278,
'longitude': 13.7543,
'elevation': 228.0,
'name': 'Dresden-Klotzsche',
'region': 'Sachsen'}],
'values': [{'station_id': '01048',
'resolution': 'daily',
'dataset': 'climate_summary',
'parameter': 'temperature_air_mean_2m',
'timestamp': '2020-01-01T00:00:00.000000+00:00',
'value': 2.4,
'quality': 10.0},
{'station_id': '01048',
'resolution': 'daily',
'dataset': 'climate_summary',
'parameter': 'temperature_air_mean_2m',
'timestamp': '2020-01-02T00:00:00.000000+00:00',
'value': 0.2,
'quality': 10.0}]}
To Json#
1from wetterdienst.provider.dwd.observation import DwdObservationRequest
2
3request = DwdObservationRequest(
4 parameters=("daily", "kl", "temperature_air_mean_2m"),
5 start="2020-01-01",
6 end="2020-01-02"
7)
8stations = request.filter_by_station_id(station_id="01048")
9values = stations.values.all()
10print(values.to_json(with_metadata=True, with_stations=True))
{
"metadata": {
"provider": {
"name_local": "Deutscher Wetterdienst",
"name_english": "German Weather Service",
"country": "Germany",
"copyright": "\u00a9 Deutscher Wetterdienst (DWD), Climate Data Center (CDC)",
"url": "https://opendata.dwd.de/climate_environment/CDC/"
},
"producer": {
"name": "wetterdienst",
"version": "0.145.0",
"repository": "https://github.com/earthobservations/wetterdienst",
"documentation": "https://wetterdienst.readthedocs.io",
"doi": "10.5281/zenodo.3960624"
}
},
"stations": [
{
"resolution": "daily",
"dataset": "climate_summary",
"station_id": "01048",
"start_timestamp": "1934-01-01T00:00:00.000000+00:00",
"end_timestamp": "2026-10-10T00:00:00.000000+00:00",
"latitude": 51.1278,
"longitude": 13.7543,
"elevation": 228.0,
"name": "Dresden-Klotzsche",
"region": "Sachsen"
}
],
"values": [
{
"station_id": "01048",
"resolution": "daily",
"dataset": "climate_summary",
"parameter": "temperature_air_mean_2m",
"timestamp": "2020-01-01T00:00:00.000000+00:00",
"value": 2.4,
"quality": 10.0
},
{
"station_id": "01048",
"resolution": "daily",
"dataset": "climate_summary",
"parameter": "temperature_air_mean_2m",
"timestamp": "2020-01-02T00:00:00.000000+00:00",
"value": 0.2,
"quality": 10.0
}
]
}
To Ogc Feature Collection#
1from wetterdienst.provider.dwd.observation import DwdObservationRequest
2
3request = DwdObservationRequest(
4 parameters=("daily", "kl", "temperature_air_mean_2m"),
5 start="2020-01-01",
6 end="2020-01-02"
7)
8stations = request.filter_by_station_id(station_id="01048")
9values = stations.values.all()
10values.to_ogc_feature_collection(with_metadata=True)
{'metadata': {'provider': {'name_local': 'Deutscher Wetterdienst',
'name_english': 'German Weather Service',
'country': 'Germany',
'copyright': '© Deutscher Wetterdienst (DWD), Climate Data Center (CDC)',
'url': 'https://opendata.dwd.de/climate_environment/CDC/'},
'producer': {'name': 'wetterdienst',
'version': '0.145.0',
'repository': 'https://github.com/earthobservations/wetterdienst',
'documentation': 'https://wetterdienst.readthedocs.io',
'doi': '10.5281/zenodo.3960624'}},
'data': {'type': 'FeatureCollection',
'features': [{'type': 'Feature',
'properties': {'resolution': 'daily',
'dataset': 'climate_summary',
'id': '01048',
'name': 'Dresden-Klotzsche',
'region': 'Sachsen',
'start_timestamp': '1934-01-01T00:00:00.000000+00:00',
'end_timestamp': '2026-10-10T00:00:00.000000+00:00'},
'geometry': {'type': 'Point', 'coordinates': [13.7543, 51.1278, 228.0]},
'values': [{'resolution': 'daily',
'dataset': 'climate_summary',
'parameter': 'temperature_air_mean_2m',
'timestamp': '2020-01-01T00:00:00.000000+00:00',
'value': 2.4,
'quality': 10.0},
{'resolution': 'daily',
'dataset': 'climate_summary',
'parameter': 'temperature_air_mean_2m',
'timestamp': '2020-01-02T00:00:00.000000+00:00',
'value': 0.2,
'quality': 10.0}]}]}}
To GeoJson#
1from wetterdienst.provider.dwd.observation import DwdObservationRequest
2
3request = DwdObservationRequest(
4 parameters=("daily", "kl", "temperature_air_mean_2m"),
5 start="2020-01-01",
6 end="2020-01-02"
7)
8stations = request.filter_by_station_id(station_id="01048")
9values = stations.values.all()
10print(values.to_geojson(with_metadata=True))
{
"metadata": {
"provider": {
"name_local": "Deutscher Wetterdienst",
"name_english": "German Weather Service",
"country": "Germany",
"copyright": "© Deutscher Wetterdienst (DWD), Climate Data Center (CDC)",
"url": "https://opendata.dwd.de/climate_environment/CDC/"
},
"producer": {
"name": "wetterdienst",
"version": "0.145.0",
"repository": "https://github.com/earthobservations/wetterdienst",
"documentation": "https://wetterdienst.readthedocs.io",
"doi": "10.5281/zenodo.3960624"
}
},
"data": {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"resolution": "daily",
"dataset": "climate_summary",
"id": "01048",
"name": "Dresden-Klotzsche",
"region": "Sachsen",
"start_timestamp": "1934-01-01T00:00:00.000000+00:00",
"end_timestamp": "2026-10-10T00:00:00.000000+00:00"
},
"geometry": {
"type": "Point",
"coordinates": [
13.7543,
51.1278,
228.0
]
},
"values": [
{
"resolution": "daily",
"dataset": "climate_summary",
"parameter": "temperature_air_mean_2m",
"timestamp": "2020-01-01T00:00:00.000000+00:00",
"value": 2.4,
"quality": 10.0
},
{
"resolution": "daily",
"dataset": "climate_summary",
"parameter": "temperature_air_mean_2m",
"timestamp": "2020-01-02T00:00:00.000000+00:00",
"value": 0.2,
"quality": 10.0
}
]
}
]
}
}
To CSV#
1from wetterdienst.provider.dwd.observation import DwdObservationRequest
2
3request = DwdObservationRequest(
4 parameters=("daily", "kl", "temperature_air_mean_2m"),
5 start="2020-01-01",
6 end="2020-01-02"
7)
8stations = request.filter_by_station_id(station_id="01048")
9values = stations.values.all()
10print(values.to_csv())
station_id,resolution,dataset,parameter,timestamp,value,quality
01048,daily,climate_summary,temperature_air_mean_2m,2020-01-01T00:00:00.000000+00:00,2.4,10.0
01048,daily,climate_summary,temperature_air_mean_2m,2020-01-02T00:00:00.000000+00:00,0.2,10.0
SQL#
Querying data using SQL is provided by an in-memory DuckDB_ database. In order to explore what is possible, please have a look at the DuckDB SQL introduction.
The result data is provided as a table called df, and the filter takes a single condition on
it, the part of a query after WHERE: a second statement, ORDER BY or LIMIT is refused,
so sort or slice the returned frame instead. The condition runs on a DuckDB connection of its
own, which cannot read files, reach the network or load extensions, and holds nothing but the
frame.
1from wetterdienst import Settings
2from wetterdienst.provider.dwd.observation import DwdObservationRequest
3
4settings = Settings(ts_shape="long", ts_humanize=True, ts_convert_units=True) # defaults
5request = DwdObservationRequest(
6 parameters=("hourly", "temperature_air", "temperature_air_2m"),
7 start="2019-01-01",
8 end="2020-01-01",
9 settings=settings
10)
11stations = request.filter_by_station_id(station_id=[1048])
12values = stations.values.all()
13df = values.filter_by_sql("parameter='temperature_air_2m' AND value < -7.0;")
14df
---------------------------------------------------------------------------
ModuleNotFoundError Traceback (most recent call last)
File ~/checkouts/readthedocs.org/user_builds/wetterdienst/checkouts/latest/src/wetterdienst/util/extras.py:110, in import_optional(module_name, what, extra)
109 try:
--> 110 return importlib.import_module(module_name)
111 except ModuleNotFoundError as e:
112 # a dotted name is a module missing inside a package that is installed (`duckdb.duckdb`, a
113 # module of our own): a broken install or a defect, not a package to install
File ~/.asdf/installs/python/3.14.6/lib/python3.14/importlib/__init__.py:88, in import_module(name, package)
87 level += 1
---> 88 return _bootstrap._gcd_import(name[level:], package, level)
File <frozen importlib._bootstrap>:1406, in _gcd_import(name, package, level)
1404 if level > 0:
1405 name = _resolve_name(name, package, level)
-> 1406 return _find_and_load(name, _gcd_import)
File <frozen importlib._bootstrap>:1371, in _find_and_load(name, import_)
1369 module = sys.modules.get(name, _NEEDS_LOADING)
1370 if module is _NEEDS_LOADING:
-> 1371 return _find_and_load_unlocked(name, import_)
1372
File <frozen importlib._bootstrap>:1335, in _find_and_load_unlocked(name, import_)
1333 spec = _find_spec(name, path)
1334 if spec is None:
-> 1335 raise ModuleNotFoundError(f'{_ERR_MSG_PREFIX}{name!r}', name=name)
1336 else:
ModuleNotFoundError: No module named 'duckdb'
The above exception was the direct cause of the following exception:
MissingDependencyError Traceback (most recent call last)
Cell In[26], line 13
9 settings=settings
10 )
11 stations = request.filter_by_station_id(station_id=[1048])
12 values = stations.values.all()
---> 13 df = values.filter_by_sql("parameter='temperature_air_2m' AND value < -7.0;")
14 df
File ~/checkouts/readthedocs.org/user_builds/wetterdienst/checkouts/latest/src/wetterdienst/io/export.py:41, in ExportMixin.filter_by_sql(self, sql)
39 def filter_by_sql(self, sql: str) -> pl.DataFrame:
40 """Filter df using an SQL query WHERE clause."""
---> 41 self.df = self._filter_by_sql(self.df, sql)
42 return self.df
File ~/checkouts/readthedocs.org/user_builds/wetterdienst/checkouts/latest/src/wetterdienst/io/export.py:259, in ExportMixin._filter_by_sql(df, sql)
232 @staticmethod
233 def _filter_by_sql(df: pl.DataFrame, sql: str) -> pl.DataFrame:
234 """Filter df using an SQL query WHERE clause.
235
236 This implementation is based on DuckDB, so please
(...) 257
258 """
--> 259 duckdb = import_optional("duckdb", "Filtering with SQL", extra="sql")
261 # every timestamp the frame carries, not the values column alone (then `date`): a stations
262 # frame has `start_timestamp` and `end_timestamp` and no `timestamp` at all, so the CLI's own
263 # `--sql "state=\'Sachsen\'"` (as it then was) -- documented as a filter on station metadata
264 # -- died on a missing column
265 zones = {name: dtype.time_zone for name, dtype in df.schema.items() if isinstance(dtype, pl.Datetime)}
File ~/checkouts/readthedocs.org/user_builds/wetterdienst/checkouts/latest/src/wetterdienst/util/extras.py:116, in import_optional(module_name, what, extra)
114 if e.name is not None and "." in e.name:
115 raise
--> 116 raise MissingDependencyError(missing_dependency_message(what, e.name or module_name, extra=extra)) from e
MissingDependencyError: Filtering with SQL requires duckdb, which is not installed. Install it with: pip install wetterdienst[sql]
Interpolation and summary at an elevation#
Air temperature falls with height – about 0.65 K per 100 m, a dew point about 0.2 – so stations at different altitudes say different things about the same weather. Interpolating them as they come fits that vertical difference as though it were horizontal: around Garmisch the stations within 40 km span 630 m to 2956 m, which is 15 K of it.
Give the point an elevation in metres and each station’s readings are brought to it first:
from wetterdienst.provider.dwd.observation import DwdObservationRequest
request = DwdObservationRequest(
parameters=[("daily", "kl", "temperature_air_mean_2m")],
start="2022-01-01",
end="2022-01-05",
)
request.interpolate(latlon=(47.48, 11.06), elevation=1500) # on the mountain
request.interpolate(latlon=(47.48, 11.06), elevation=200) # in the valley
summarize takes the same argument, where it matters more still: a summary answers with one
station’s reading rather than a blend, so nothing softens the difference in altitude.
interpolate_by_station_id and summarize_by_station_id answer at the named station’s own
altitude unless told another one, that being the one case where the elevation is known without
being given. For the reading uncorrected, pass the station’s coordinates to interpolate.
A station whose own elevation the provider does not report cannot be placed against the elevation asked for, so it is left out rather than contributing at its own altitude while its neighbours are moved. Some providers report no station elevations at all, in which case an elevation leaves nothing to interpolate from for the quantities that depend on it.
Left out, nothing is corrected. The elevation cannot be derived from the stations themselves: an elevation taken from the same linear interpolation cancels out of the correction exactly, leaving the result unchanged, so it has to come from the caller.
Export#
Data can be exported to SQLite, DuckDB, InfluxDB, CrateDB and more targets. A target is identified by a connection string.
Examples:
sqlite:///dwd.sqlite?table=weather
duckdb:///dwd.duckdb?table=weather
influxdb://localhost/?database=dwd&table=weather
crate://localhost/?database=dwd&table=weather
File targets are identified by their extension:
file:///path/to/dwd.csv
file:///path/to/dwd.json
file:///path/to/dwd.jsonl
file:///path/to/dwd.xlsx
file:///path/to/dwd.parquet
file:///path/to/dwd.feather
file:///path/to/dwd.zarr
file:///path/to/dwd.nc
CSV and spreadsheets are written the way to_csv returns them, with timestamps as ISO strings
and a list of station ids as one comma-separated field. JSON and JSON Lines hold the same records
with the list kept as a list, JSON having arrays of its own, rather than the response envelope
to_json wraps them in. Zarr and NetCDF go through xarray, grouped by the datasets the frame holds;
NetCDF writes its timestamps as CF units, Zarr as the nanoseconds since the epoch it reads back.
from wetterdienst import Settings
from wetterdienst.provider.dwd.observation import DwdObservationRequest
request = DwdObservationRequest(
parameters=("hourly", "temperature_air"),
start="2019-01-01",
end="2020-01-01",
)
stations = request.filter_by_station_id(station_id=[1048, 1050])
stations.values.to_target("influxdb://localhost/?database=dwd&table=weather", if_exists="append")
The previous example uses a batch approach meaning each station is written one by one. The first station is written
with the if_exists given and every station after it with append, so the sink has to accept append — which is why
this form passes it explicitly rather than taking the fail that TimeseriesValues.to_target defaults to.
You could also first collect all data and then write it at once:
from wetterdienst import Settings
from wetterdienst.provider.dwd.observation import DwdObservationRequest
request = DwdObservationRequest(
parameters=("hourly", "temperature_air"),
start="2019-01-01",
end="2020-01-01",
)
stations = request.filter_by_station_id(station_id=[1048, 1050])
stations.values.all().to_target("influxdb://localhost/?database=dwd&table=weather")
You could also iterate over the stations and write them one by one:
from wetterdienst import Settings
from wetterdienst.provider.dwd.observation import DwdObservationRequest
request = DwdObservationRequest(
parameters=("hourly", "temperature_air"),
start="2019-01-01",
end="2020-01-01",
)
stations = request.filter_by_station_id(station_id=[1048, 1050])
for station in stations.values.query():
station.to_target("influxdb://localhost/?database=dwd&table=weather", if_exists="append")
The argument if_exists supports the following modes:
fail: Raise an error if the table/file already exists.replace: Drop the table/file before inserting new values.append: Insert new values to the existing table (not supported by files).skip: Do nothing if the table/file already exists.
replace is the default on a result — StationsResult, ValuesResult, anything carrying a frame.
TimeseriesValues.to_target, the batch form above, defaults to fail instead and then writes
every station after the first with append, because there it is one table being filled station by
station rather than one frame being written.
InfluxDB takes replace and append, which do the same thing there: every write is points, and a
point carrying the timestamp and tags another already has replaces that one, so replace does not
clear what is already in the measurement. fail and skip are refused, because both turn on
whether the measurement exists and this sink never asks.
An append into a database matches columns by name rather than by position, so a frame carrying a column the table does not have is refused instead of being filed under whatever heading sat in that position. A frame that is a subset of the table’s columns is accepted, with nulls for the rest.
Every refusal — a mode the sink does not do, a target that already holds data under fail, or a
format or protocol nothing here writes — raises ExportRefusedError (from wetterdienst.exceptions),
whose message is the whole of what there is to know. A package a sink needs that is not installed
(DuckDB, SQLAlchemy, xarray) raises MissingDependencyError, an ImportError naming the extra
that installs it. Anything else out of to_target is a defect or an environment problem and keeps
its own class and traceback.
The CLI takes the same argument as --if_exists, which is what a scheduled acquisition needs —
see Scheduling:
wetterdienst values --provider=dwd --network=observation \
--parameters=daily/kl/temperature_air_mean_2m --periods=recent --station=01048 \
--target="duckdb:////var/lib/wetterdienst/obs.duckdb?table=weather" --if_exists=append
Caching#
The backbone of wetterdienst uses fsspec caching. It requires to create a directory under /home for the
most cases. If you are not allowed to write into /home you will run into OSError. For this purpose you can set
an environment variable WD_CACHE_DIR to define the place where the caching directory should be created.
To find out where your cache is located you can use the following code:
1from wetterdienst import Settings
2
3settings = Settings()
4settings.cache_dir
Or similarly with the cli:
!wetterdienst cache
FSSPEC#
FSSPEC is used for flexible file caching. It relies on the two libraries requests and aiohttp. Aiohttp is used for
asynchronous requests and may swallow some errors related to proxies, ssl or similar. Use the
setting fsspec_client_kwargs (environment variable WD_FSSPEC_CLIENT_KWARGS) to pass your very own client kwargs
to fsspec, which are merged into the default ones (see Settings) e.g.
1from wetterdienst import Settings
2from wetterdienst.provider.dwd.observation import DwdObservationRequest
3
4settings = Settings(fsspec_client_kwargs={"trust_env": True}) # use proxy from environment variables
5
6request = DwdObservationRequest(
7 parameters=("hourly", "temperature_air"),
8 settings=settings
9)
10stations = request.filter_by_station_id(station_id=[1048])
11stations