{"id":23062462,"url":"https://github.com/fl550/simple_dwd_weatherforecast","last_synced_at":"2026-04-19T14:12:58.906Z","repository":{"id":40442555,"uuid":"276404892","full_name":"FL550/simple_dwd_weatherforecast","owner":"FL550","description":"This is a python package for simple access to hourly forecast data for the next 10 days and reported weather where this data is provided.","archived":false,"fork":false,"pushed_at":"2025-03-24T07:52:24.000Z","size":1451,"stargazers_count":32,"open_issues_count":1,"forks_count":27,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-03-28T14:08:04.480Z","etag":null,"topics":["deutscher-wetterdienst","deutscherwetterdienst","dwd","forecast","forecast-data","weather-data","weather-forecast"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/FL550.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-07-01T14:49:36.000Z","updated_at":"2025-03-26T14:28:22.000Z","dependencies_parsed_at":"2023-09-26T22:55:44.555Z","dependency_job_id":"5c90aa46-5f36-478b-8be2-4a22505f9e73","html_url":"https://github.com/FL550/simple_dwd_weatherforecast","commit_stats":{"total_commits":136,"total_committers":6,"mean_commits":"22.666666666666668","dds":"0.11029411764705888","last_synced_commit":"35c07ff993f7d1cb05ac59304a6ce8163bfc8844"},"previous_names":[],"tags_count":86,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FL550%2Fsimple_dwd_weatherforecast","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FL550%2Fsimple_dwd_weatherforecast/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FL550%2Fsimple_dwd_weatherforecast/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FL550%2Fsimple_dwd_weatherforecast/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/FL550","download_url":"https://codeload.github.com/FL550/simple_dwd_weatherforecast/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247198461,"owners_count":20900080,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["deutscher-wetterdienst","deutscherwetterdienst","dwd","forecast","forecast-data","weather-data","weather-forecast"],"created_at":"2024-12-16T03:27:08.085Z","updated_at":"2026-04-19T14:12:58.898Z","avatar_url":"https://github.com/FL550.png","language":"Python","funding_links":["https://www.buymeacoffee.com/FL550"],"categories":[],"sub_categories":[],"readme":"# Simple DWD weather forecast\n\n[![BuyMeCoffee][buymecoffeebadge]][buymecoffee]\n\nDISCLAIMER: This project is a private open source project and doesn't have any connection with Deutscher Wetterdienst.\n\n**If you like my work, I would be really happy if you buy me some coffee: [Buy Me A Coffee][buymecoffee]**\n\n## Weather data\n\nThis is a python package for simple access to hourly forecast data for the next 10 days. The data is updated every six hours and updated when needed. Some stations also have actual reported weather, which you can also retrieve.\n\nAvailable station-IDs can be found [here](simple_dwd_weatherforecast/stations.json) or you can use the method `dwdforecast.get_nearest_station_id(latitude, longitude)` which tries to find it for you.\n\nForecasted weather conditions are evaluated using this [table](https://www.dwd.de/DE/leistungen/opendata/help/schluessel_datenformate/kml/mosmix_element_weather_xls.xlsx?__blob=publicationFile\u0026v=4) and then converted into these possible weather conditions:\n\n- cloudy\n- fog\n- lightning-rainy\n- partlycloudy\n- pouring\n- rainy\n- snowy\n- snowy-rainy\n- sunny\n\nThe reported weather is delayed (roughly one hour), so have a close look at the time within the presented data.\n\nThe weather report for the region which is available on the DWD homepage (see an example [here](https://www.dwd.de/DWD/wetter/wv_allg/deutschland/text/vhdl13_dwoh.html)) can also be retrieved via a method which maps the station to the relevant region.\n\n## Weather maps\n\nYou can also retrieve weather maps from the DWD GeoServer with this package.\n\n## Airquality data\n\nAirquality stations are also available. Values are reported for Stickstoffdioxid', 'Ozon', 'PM10', 'PM2_5' if available.\n\n## Installation\n\n```python\npython3 -m pip install simple_dwd_weatherforecast\n```\n\nThis installs the portable core features and works on platforms where native\necCodes libraries are not available (for example many Home Assistant OS setups).\n\nIf you also need apparent/perceived temperature parsing from GRIB2 files, install\nthe optional extra:\n\n```python\npython3 -m pip install \"simple_dwd_weatherforecast[apparent-temperature]\"\n```\n\nIf the platform does not provide a compatible ecCodes runtime, the core package\nstill works and apparent temperature methods will return no value.\n\n## Usage\n\n### Weather data\n\n#### Usage example\n```python\nfrom simple_dwd_weatherforecast import dwdforecast\nfrom datetime import datetime, timedelta, timezone\n\n#Find nearest Station-ID automatically\n#id = dwdforecast.get_nearest_station_id(50.1109221, 8.6821267)\n\ndwd_weather = dwdforecast.Weather(\"10385\") # Station-ID For BERLIN-SCHOENEFELD\ntime_now = datetime.now(timezone.utc)\ntemperature_now = dwd_weather.get_forecast_data(dwdforecast.WeatherDataType.TEMPERATURE, time_now)\napparent_temperature_now = dwd_weather.get_apparent_temperature()\napparent_temperature_hourly = dwd_weather.get_apparent_temperature_forecast()\n# Example format:\n# {\n#   \"2026-04-04T15:00:00.000Z\": 16.5,\n#   \"2026-04-04T16:00:00.000Z\": 16.5,\n# }\ntime_tomorrow = datetime.now(timezone.utc)+timedelta(days=1)\ntemperature_tomorrow = dwd_weather.get_forecast_data(dwdforecast.WeatherDataType.TEMPERATURE, time_tomorrow)\n```\n\n#### Available methods\n\nAll methods return their values as string. The datetime value has to be in UTC. If no data is available for this datetime, None will be returned. With the optional boolean `shouldUpdate` an automated check for new updates can be prevented by setting this parameter to `False`. Otherwise data is updated if new data is available with every function call.\n\nThere is also data available which is updated every hour by DWD. Be careful though, as this will download each time roughly 37MB of data. Furthermore, some elements are missing from this data:\n- PRECIPITATION_PROBABILITY\n- PRECIPITATION_DURATION\n\nYou can use this data by using the optional parameter `force_hourly=True`.\n\n```python\nis_valid_station_id(station_id) #Checks if given station_id is valid\n\nget_nearest_station_id(latitude, longitude) #Returns nearest Station-ID for the coordinates. latitude and longitude expect float values.\n\nupdate(optional bool force_hourly) #Force fetch of new data from the DWD server. With this parameter set to True, there will be missing the precipitation forecast. See above.\n\nclass WeatherDataType(Enum):\n    CONDITION = \"condition\"\n    TEMPERATURE = \"TTT\" # Unit: K\n    DEWPOINT = \"Td\" # Unit: K\n    PRESSURE = \"PPPP\" # Unit: Pa\n    WIND_SPEED = \"FF\" # Unit: m/s\n    WIND_DIRECTION = \"DD\" # Unit: Degrees\n    WIND_GUSTS = \"FX1\" # Unit: m/s\n    PRECIPITATION = \"RR1c\" # Unit: kg/m2\n    PRECIPITATION_PROBABILITY = \"wwP\" # Unit: % (0..100)\n    PRECIPITATION_DURATION = \"DRR1\" # Unit: s\n    CLOUD_COVERAGE = \"N\" # Unit: % (0..100)\n    VISIBILITY = \"VV\" # Unit: m\n    SUN_DURATION = \"SunD1\" # Unit: s\n    SUN_IRRADIANCE = \"Rad1h\" # Unit: W/m2\n    FOG_PROBABILITY = \"wwM\" # Unit: % (0..100)\n    HUMIDITY = \"humidity\"  # Unit: %\n    EVAPORATION = (\"PEvap\", \"evaporation\")  # In the last 24h Unit: kg/m2\n\nclass Weather:\n\n    get_station_name(optional bool shouldUpdate) # Return Station name\n\n    get_forecast_data(weatherDataType: see WeatherDataType, datetime, optional bool shouldUpdate) # Returns the requested weather data\n\n    get_get_reported_weather(weatherDataType: see WeatherDataType, optional bool shouldUpdate) # Returns the latest actual reported value if available for this station\n\n    get_daily_max(weatherDataType: see WeatherDataType, datetime, optional bool shouldUpdate) # Returns the maximum daily value\n\n    get_timeframe_max(weatherDataType: see WeatherDataType, datetime, timeframe: hours after datetime as int, optional bool shouldUpdate) # Returns the maximum of that value within the time frame\n\n    get_daily_min(weatherDataType: see WeatherDataType, datetime, optional bool shouldUpdate) # Returns the minimum daily value\n\n    get_timeframe_min(weatherDataType: see WeatherDataType, datetime, timeframe: hours after datetime as int, optional bool shouldUpdate) # Returns the minimum of that value within the time frame\n\n    get_daily_sum(weatherDataType: see WeatherDataType, datetime, optional bool shouldUpdate) # Returns the daily sum of that value\n\n    get_timeframe_sum(weatherDataType: see WeatherDataType, datetime, timeframe: hours after datetime as int, optional bool shouldUpdate) # Returns the sum of that value within the time frame\n\n    get_daily_avg(weatherDataType: see WeatherDataType, datetime, optional bool shouldUpdate) # Returns the daily average of that value\n\n    get_timeframe_avg(weatherDataType: see WeatherDataType, datetime, timeframe: hours after datetime as int, optional bool shouldUpdate) # Returns the average of that value within the time frame\n\n    get_forecast_condition(datetime, optional bool shouldUpdate) # Result is condition as text\n\n    get_daily_condition(datetime, optional bool shouldUpdate) # Result is an approximate \"feeled\" condition at this day\n\n    get_timeframe_condition(datetime, timeframe: hours after datetime as int, optional bool shouldUpdate) # Result is an approximate \"feeled\" condition at this time frame\n\n    get_weather_report(optional bool shouldUpdate) # Returns the weather report for the geographical region of the station as HTML\n\n    get_uv_index(int day_from_today (values: 0-2)) # Returns the UV index for the nearest station available for today, tomorrow or the day after tomorrow\n\n    supports_apparent_temperature() # Returns True if GRIB/eccodes backend is available\n\n    get_apparent_temperature_unavailable_reason() # Returns None when available, otherwise a human-readable reason\n\n    get_apparent_temperature(optional bool shouldUpdate) # Returns current apparent/perceived temperature (gefuehlte Temperatur) in °C\n\n    get_apparent_temperature_forecast(optional bool shouldUpdate) # Returns hourly apparent/perceived temperature forecast as dict[{timestamp_utc: value_celsius}] starting from current UTC hour\n\n    update(self, optional bool force_hourly (default: False), optional bool with_forecast (default: True), optional bool with_measurements (default: False), optional bool with_report (default: False), optional bool with_uv (default: True), optional bool with_apparent_temperature (default: False)) # Updates the weather data\n\n    # {datetime(2025,4,1,12,0,UTC): 3.6, datetime(2025,4,1,12,5,UTC): 0.0, ...}\n    get_radar_precipitation_forecast()\n\n    # {'start': datetime(...), 'end': datetime(...), 'length': timedelta(minutes=25),\n    #  'max': 7.2, 'sum': 1.4}\n    get_radar_next_precipitation()\n\n    # Override coordinates to any German location\n    get_radar_precipitation_forecast(lat=52.52, lon=13.41)\n```\n\n#### Home Assistant style capability check\n\nUse the capability helpers to expose apparent temperature only when the optional\nGRIB backend is available on the target platform.\n\n```python\nfrom simple_dwd_weatherforecast import dwdforecast\n\nweather = dwdforecast.Weather(\"10385\")\n\nif weather.supports_apparent_temperature():\n    value = weather.get_apparent_temperature()\n    # publish apparent temperature sensor state\nelse:\n    reason = weather.get_apparent_temperature_unavailable_reason()\n    # skip creating the sensor or mark it unavailable with `reason`\n```\n\n#### Advanced Usage\n\nIf you want to access the forecast data for the next 10 days directly for further processing, you can do so. All data is stored in dictonary and can be accessed like this:\n\n```python\nfrom simple_dwd_weatherforecast import dwdforecast\n\ndwd_weather​ ​=​ ​dwdforecast​.​Weather​(​\"10385\"​) # Station-ID For BERLIN-SCHOENEFELD​\ndwd_weather.update() # This has to be done manually to fetch the data from the DWD server\naccess_forecast_dict = dwd_weather.forecast_data # dwd_weather.forecast_data contains the forecast as a dict\n```\n\nKeep in mind that the weather condition is stored as the original digit value as provided by DWD. So if you want to use them, you have to convert these yourself. You can use my simplified conversion from the source code in the variable `weather_codes` or the original conversion available [here](https://www.dwd.de/DE/leistungen/opendata/help/schluessel_datenformate/kml/mosmix_element_weather_xls.xlsx?__blob=publicationFile\u0026v=4).\n\n### Weather maps\n\nYou can download weather maps from the DWD GeoServer with this package. There are different options for the displayed foreground and background data. See below for further information.\n\n![example picture of a map produced with this package](/map_example.png?raw=true \"Example picture of a map produced with this package\")\n\n#### Usage example\n```python\nfrom simple_dwd_weatherforecast import dwdmap\n\nimage = dwdmap.get_from_location(51.272, 8.84, radius_km=100, map_type=dwdmap.WeatherMapType.NIEDERSCHLAGSRADAR, background_type=dwdmap.WeatherBackgroundMapType.BUNDESLAENDER)\n\nimage.save(\"niederschlag.png\")\n\nimage = dwdmap.get_germany(map_type=dwdmap.WeatherMapType.UVINDEX, image_width=520, image_height=580)\n\nimage.save(\"uvindex.png\")\n```\n\n#### Available methods\n\n```python\n\nclass WeatherMapType(Enum):\n    NIEDERSCHLAGSRADAR = \"dwd:Niederschlagsradar\"\n    MAXTEMP = \"dwd:GefuehlteTempMax\"\n    UVINDEX = \"dwd:UVI_CS\"\n    POLLENFLUG = \"dwd:Pollenflug\"\n    SATELLITE_RGB = \"dwd:Satellite_meteosat_1km_euat_rgb_day_hrv_and_night_ir108_3h\"\n    SATELLITE_IR = \"dwd:Satellite_worldmosaic_3km_world_ir108_3h\"\n    WARNUNGEN_GEMEINDEN = \"dwd:Warnungen_Gemeinden\"\n    WARNUNGEN_KREISE = \"dwd:Warnungen_Landkreise\"\n\nclass WeatherBackgroundMapType(Enum):\n    LAENDER = \"dwd:Laender\"\n    BUNDESLAENDER = \"dwd:Warngebiete_Bundeslaender\"\n    KREISE = \"dwd:Warngebiete_Kreise\"\n    GEMEINDEN = \"dwd:Warngebiete_Gemeinden\"\n    SATELLIT = \"dwd:bluemarble\"\n\nclass MarkerShape(Enum):\n    CIRCLE = \"circle\"\n    SQUARE = \"square\"\n    CROSS = \"cross\"\n\nclass Marker(\n        latitude: float,\n        longitude: float,\n        shape: MarkerShape,\n        size: int,\n        colorRGB: tuple[int, int, int],\n        width: int = 0,\n    )\n\nget_from_location(longitude, latitude, radius_km, map_type: [WeatherMapType], background_type: [WeatherBackgroundMapType], optional integer image_width, optional integer image_height, optional markers: list[Marker], optional bool dark_mode) #Returns map as pillow image with given radius from coordinates\n\nget_germany(map_type: [WeatherMapType], optional [WeatherBackgroundMapType] background_type, optional integer image_width, optional integer image_height, optional markers: list[Marker], optional bool dark_mode) #Returns map as pillow image of whole germany\n\nget_map(minx,miny,maxx,maxy, map_type: [WeatherMapType], background_type: [WeatherBackgroundMapType], optional integer image_width, optional integer image_height, optional markers: list[Marker], optional bool dark_mode) #Returns map as pillow image\n```\n\n\n### Image loop\n\nThere is also the possibility to retrieve multiple images as a loop. This can be done by the class ImageLoop. This however only works for the precipitation radar.\n\n\n#### Usage example\n```python\nfrom simple_dwd_weatherforecast import dwdmap\n\nmaploop = dwdmap.ImageLoop(\n    dwdmap.germany_boundaries.minx,\n    dwdmap.germany_boundaries.miny,\n    dwdmap.germany_boundaries.maxx,\n    dwdmap.germany_boundaries.maxy,\n    [dwdmap.WeatherMapType.NIEDERSCHLAGSRADAR],\n    [dwdmap.WeatherBackgroundMapType.BUNDESLAENDER],\n    steps=5,\n)\n\nfor image in enumerate(maploop._images):\n    image[1].save(f\"image{image[0]}.png\")\n\n```\n\n#### Available methods\n\n```python\nImageLoop(minx: float, miny: float, maxx: float, maxy: float, map_type: [WeatherMapType], background_type: [WeatherBackgroundMapType],\n        steps: int = 6, image_width: int = 520,image_height: int = 580, markers: list[Marker] = [], optional bool dark_mode) -\u003e ImageLoop\n\nget_images() -\u003e Iterable[ImageFile.ImageFile] # Returns the image loop\n\nupdate() # Updates the loop to the most recent files\n\n```\n\n\n### Airquality stations\n\nYou can download airquality measurements and forecasts with this package. You can get daily and hourly forecast data.\n\n#### Usage example\n```python\nimport asyncio\n\nfrom simple_dwd_weatherforecast.dwdairquality import AirQuality\n\nstation = asyncio.run(\n    AirQuality.get_station_from_location(53.092022, 8.127382, \"hourly\")\n)\n\nstation.update()\n\nprint(station.get_current(AirQualityDataType.OZON))\nprint(station.get_forecast(AirQualityDataType.PM10))\n```\n\n```python\nclass AirQualityDataType(Enum):\n    STICKSTOFFDIOXID = \"Stickstoffdioxid\"\n    OZON = \"Ozon\"\n    PM10 = \"PM10\"\n    PM2_5 = \"PM2_5\"\n\n```\n\n## Help and Contribution\n\nFeel free to open an issue if you find one and I will do my best to help you. If you want to contribute, your help is appreciated! If you want to add a new feature, add a pull request first so we can chat about the details.\n\nBefore pushing changes, tests must pass locally.\n\n1. Enable the repository hooks once:\n\n    ```bash\n    git config core.hooksPath .githooks\n    ```\n\n2. Push as usual. The pre-push hook runs:\n\n    ```bash\n    python -m unittest discover -q -f tests\n    ```\n\nIf tests fail, the push is blocked.\n\n## Licenses\n\nThis package uses public data from [DWD OpenData](https://www.dwd.de/DE/leistungen/opendata/opendata.html). The Copyright can be viewed [here](https://www.dwd.de/DE/service/copyright/copyright_node.html).\n\n[buymecoffee]: https://www.buymeacoffee.com/FL550\n[buymecoffeebadge]: https://img.shields.io/badge/buy%20me%20a%20coffee-donate-yellow?style=for-the-badge\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffl550%2Fsimple_dwd_weatherforecast","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffl550%2Fsimple_dwd_weatherforecast","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffl550%2Fsimple_dwd_weatherforecast/lists"}