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:

  • parameters

  • start

  • end

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
shape: (6_156, 10)
resolutiondatasetstation_idstart_timestampend_timestamplatitudelongitudeelevationnameregion
strstrstrdatetime[μs, UTC]datetime[μs, UTC]f64f64f64strstr
"daily""precipitation_more""00001"1912-01-01 00:00:00 UTC1986-06-30 00:00:00 UTC47.84138.8493478.0"Aach""Baden-Württemberg"
"daily""precipitation_more""00002"1951-01-01 00:00:00 UTC2006-12-31 00:00:00 UTC50.80666.0996138.0"Aachen (Kläranlage)""Nordrhein-Westfalen"
"daily""precipitation_more""00003"1891-01-01 00:00:00 UTC2011-03-31 00:00:00 UTC50.78276.0941202.0"Aachen""Nordrhein-Westfalen"
"daily""precipitation_more""00004"1951-01-01 00:00:00 UTC1979-10-31 00:00:00 UTC50.76836.1207243.0"Aachen-Brand""Nordrhein-Westfalen"
"daily""precipitation_more""00006"1982-11-01 00:00:00 UTC2026-10-10 00:00:00 UTC48.836110.0598455.0"Aalen-Unterrombach""Baden-Württemberg"
…………………………
"daily""precipitation_more""20107"2025-03-27 00:00:00 UTC2026-10-10 00:00:00 UTC49.96876.8272311.0"Wittlich-Bergweiler""Rheinland-Pfalz"
"daily""precipitation_more""20108"2025-05-01 00:00:00 UTC2026-10-10 00:00:00 UTC51.15436.504956.0"Mönchengladbach-Schelsen""Nordrhein-Westfalen"
"daily""precipitation_more""20110"1969-01-01 00:00:00 UTC2005-07-31 00:00:00 UTC52.318311.567764.0"Colbitz""Sachsen-Anhalt"
"daily""precipitation_more""20189"2025-10-01 00:00:00 UTC2026-04-30 00:00:00 UTC47.675912.47687.0"Reit im Winkl (Kaiserweg)""Bayern"
"daily""precipitation_more""20191"2025-09-01 00:00:00 UTC2026-10-10 00:00:00 UTC50.923814.357375.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
shape: (1, 10)
resolutiondatasetstation_idstart_timestampend_timestamplatitudelongitudeelevationnameregion
strstrstrdatetime[μs, UTC]datetime[μs, UTC]f64f64f64strstr
"daily""precipitation_more""01048"1926-04-25 00:00:00 UTC2026-10-10 00:00:00 UTC51.127813.7543228.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
shape: (1, 10)
resolutiondatasetstation_idstart_timestampend_timestamplatitudelongitudeelevationnameregion
strstrstrdatetime[μs, UTC]datetime[μs, UTC]f64f64f64strstr
"daily""precipitation_more""01048"1926-04-25 00:00:00 UTC2026-10-10 00:00:00 UTC51.127813.7543228.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
shape: (3, 10)
resolutiondatasetstation_idstart_timestampend_timestamplatitudelongitudeelevationnameregion
strstrstrdatetime[μs, UTC]datetime[μs, UTC]f64f64f64strstr
"daily""precipitation_more""01047"1828-01-01 00:00:00 UTC1992-11-30 00:00:00 UTC51.055713.7274112.0"Dresden (Mitte)""Sachsen"
"daily""precipitation_more""01048"1926-04-25 00:00:00 UTC2026-10-10 00:00:00 UTC51.127813.7543228.0"Dresden-Klotzsche""Sachsen"
"daily""precipitation_more""01049"1995-08-01 00:00:00 UTC2004-12-31 00:00:00 UTC51.058213.6694147.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
shape: (6, 11)
resolutiondatasetstation_idstart_timestampend_timestamplatitudelongitudeelevationnameregiondistance
strstrstrdatetime[μs, UTC]datetime[μs, UTC]f64f64f64strstrf64
"hourly""temperature_air""01975"1949-01-01 00:00:00 UTC2026-10-10 00:00:00 UTC53.63329.988111.0"Hamburg-Fuhlsbüttel""Hamburg"9.1381
"hourly""temperature_air""01981"2005-03-01 00:00:00 UTC2026-10-10 00:00:00 UTC53.47769.89574.0"Hamburg-Neuwiedenthal""Hamburg"10.4279
"hourly""temperature_air""00052"1976-01-01 00:00:00 UTC1988-01-01 00:00:00 UTC53.662310.19946.0"Ahrensburg-Wulfsdorf""Schleswig-Holstein"18.3417
"hourly""temperature_air""00760"2017-12-01 00:00:00 UTC2026-10-10 00:00:00 UTC53.36299.943583.0"Rosengarten-Klecken""Niedersachsen"21.1875
"hourly""temperature_air""04039"1988-01-11 00:00:00 UTC2026-10-10 00:00:00 UTC53.73319.877611.0"Quickborn""Schleswig-Holstein"21.6373
"hourly""temperature_air""04857"2004-09-01 00:00:00 UTC2026-10-10 00:00:00 UTC53.55349.60972.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
shape: (8, 11)
resolutiondatasetstation_idstart_timestampend_timestamplatitudelongitudeelevationnameregiondistance
strstrstrdatetime[μs, UTC]datetime[μs, UTC]f64f64f64strstrf64
"hourly""temperature_air""01975"1949-01-01 00:00:00 UTC2026-10-10 00:00:00 UTC53.63329.988111.0"Hamburg-Fuhlsbüttel""Hamburg"9.1381
"hourly""temperature_air""01981"2005-03-01 00:00:00 UTC2026-10-10 00:00:00 UTC53.47769.89574.0"Hamburg-Neuwiedenthal""Hamburg"10.4279
"hourly""temperature_air""00052"1976-01-01 00:00:00 UTC1988-01-01 00:00:00 UTC53.662310.19946.0"Ahrensburg-Wulfsdorf""Schleswig-Holstein"18.3417
"hourly""temperature_air""00760"2017-12-01 00:00:00 UTC2026-10-10 00:00:00 UTC53.36299.943583.0"Rosengarten-Klecken""Niedersachsen"21.1875
"hourly""temperature_air""04039"1988-01-11 00:00:00 UTC2026-10-10 00:00:00 UTC53.73319.877611.0"Quickborn""Schleswig-Holstein"21.6373
"hourly""temperature_air""04857"2004-09-01 00:00:00 UTC2026-10-10 00:00:00 UTC53.55349.60972.0"Mittelnkirchen-Hohenfelde""Niedersachsen"25.367
"hourly""temperature_air""05280"2007-03-01 00:00:00 UTC2026-10-10 00:00:00 UTC53.922410.226733.0"Wittenborn""Schleswig-Holstein"44.0408
"hourly""temperature_air""01736"2002-01-24 00:00:00 UTC2026-10-10 00:00:00 UTC53.573110.679726.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
shape: (637, 11)
resolutiondatasetstation_idstart_timestampend_timestamplatitudelongitudeelevationnameregiondistance
strstrstrdatetime[μs, UTC]datetime[μs, UTC]f64f64f64strstrf64
"hourly""temperature_air""01975"1949-01-01 00:00:00 UTC2026-10-10 00:00:00 UTC53.63329.988111.0"Hamburg-Fuhlsbüttel""Hamburg"9.1381
"hourly""temperature_air""01981"2005-03-01 00:00:00 UTC2026-10-10 00:00:00 UTC53.47769.89574.0"Hamburg-Neuwiedenthal""Hamburg"10.4279
"hourly""temperature_air""00052"1976-01-01 00:00:00 UTC1988-01-01 00:00:00 UTC53.662310.19946.0"Ahrensburg-Wulfsdorf""Schleswig-Holstein"18.3417
"hourly""temperature_air""00760"2017-12-01 00:00:00 UTC2026-10-10 00:00:00 UTC53.36299.943583.0"Rosengarten-Klecken""Niedersachsen"21.1875
"hourly""temperature_air""04039"1988-01-11 00:00:00 UTC2026-10-10 00:00:00 UTC53.73319.877611.0"Quickborn""Schleswig-Holstein"21.6373
……………………………
"hourly""temperature_air""03730"1948-01-01 00:00:00 UTC2026-10-10 00:00:00 UTC47.398410.2759806.0"Oberstdorf""Bayern"684.4374
"hourly""temperature_air""05792"1950-01-01 00:00:00 UTC2026-10-10 00:00:00 UTC47.42110.98482956.0"Zugspitze""Bayern"685.2131
"hourly""temperature_air""07325"2011-09-01 00:00:00 UTC2015-12-31 00:00:00 UTC47.416510.97952650.0"Schneefernerhaus""Bayern"685.6732
"hourly""temperature_air""00361"1948-01-01 00:00:00 UTC1976-01-01 00:00:00 UTC47.634413.0109550.0"Berchtesgaden (KKst)""Bayern"691.3582
"hourly""temperature_air""19856"2024-08-01 00:00:00 UTC2026-10-10 00:00:00 UTC47.613412.9819625.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
shape: (0, 10)
resolutiondatasetstation_idstart_timestampend_timestamplatitudelongitudeelevationnameregion
strstrstrdatetime[μs, UTC]datetime[μs, UTC]f64f64f64strstr

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
shape: (108_576, 7)
station_idresolutiondatasetparametertimestampvaluequality
strstrstrstrdatetime[μs, UTC]f64f64
"00003""daily""climate_summary""cloud_cover_total"1990-01-01 00:00:00 UTC1.010.0
"00003""daily""climate_summary""cloud_cover_total"1990-01-02 00:00:00 UTC1.010.0
"00003""daily""climate_summary""cloud_cover_total"1990-01-03 00:00:00 UTC0.587510.0
"00003""daily""climate_summary""cloud_cover_total"1990-01-04 00:00:00 UTC0.7510.0
"00003""daily""climate_summary""cloud_cover_total"1990-01-05 00:00:00 UTC0.962510.0
…………………
"00003""daily""climate_summary""wind_speed"2011-03-27 00:00:00 UTC1.710.0
"00003""daily""climate_summary""wind_speed"2011-03-28 00:00:00 UTC1.710.0
"00003""daily""climate_summary""wind_speed"2011-03-29 00:00:00 UTC1.610.0
"00003""daily""climate_summary""wind_speed"2011-03-30 00:00:00 UTC3.810.0
"00003""daily""climate_summary""wind_speed"2011-03-31 00:00:00 UTC7.010.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
shape: (290_099, 7)
station_idresolutiondatasetparametertimestampvaluequality
enumenumenumenumdatetime[μs, UTC]f64f64
"00003""daily""climate_summary""cloud_cover_total"1990-01-01 00:00:00 UTC1.010.0
"00003""daily""climate_summary""cloud_cover_total"1990-01-02 00:00:00 UTC1.010.0
"00003""daily""climate_summary""cloud_cover_total"1990-01-03 00:00:00 UTC0.587510.0
"00003""daily""climate_summary""cloud_cover_total"1990-01-04 00:00:00 UTC0.7510.0
"00003""daily""climate_summary""cloud_cover_total"1990-01-05 00:00:00 UTC0.962510.0
…………………
"01048""daily""solar""sunshine_duration"2019-12-28 00:00:00 UTC0.01.0
"01048""daily""solar""sunshine_duration"2019-12-29 00:00:00 UTC22320.01.0
"01048""daily""solar""sunshine_duration"2019-12-30 00:00:00 UTC5400.01.0
"01048""daily""solar""sunshine_duration"2019-12-31 00:00:00 UTC3960.01.0
"01048""daily""solar""sunshine_duration"2020-01-01 00:00:00 UTC14040.01.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    ┆        ┆         │
└────────────┴────────────┴─────────────────┴─────────────────┴─────────────────┴────────┴─────────┘
shape: (2, 11)
resolutiondatasetstation_idstart_timestampend_timestamplatitudelongitudeelevationnameregiondistance
strstrstrdatetime[μs, UTC]datetime[μs, UTC]f64f64f64strstrf64
"daily""climate_summary""05426"1953-01-01 00:00:00 UTC2026-10-10 00:00:00 UTC49.37588.1212553.0"Weinbiet""Rheinland-Pfalz"19.8177
"daily""climate_summary""04177"1948-01-01 00:00:00 UTC2026-10-10 00:00:00 UTC48.97268.3301116.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