Skip to content

Commit

Permalink
Adds weather maps to package
Browse files Browse the repository at this point in the history
  • Loading branch information
FL550 committed Jun 28, 2023
1 parent 71ff631 commit f7464f5
Show file tree
Hide file tree
Showing 6 changed files with 119 additions and 5 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ init
dist
htmlcov
__init__.pyc
development/secret.py
development/secret.py
custom_test.py
57 changes: 54 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

DISCLAIMER: This project is a private open source project and doesn't have any connection with Deutscher Wetterdienst.

## Weather data

This 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.

Available station-IDs can be found [here](simple_dwd_weatherforecast/stations.py) in the third column or you can use the method `dwdforecast.get_nearest_station_id(latitude, longitude)` which tries to find it for you.
Expand All @@ -22,6 +24,10 @@ Forecasted weather conditions are evaluated using this [table](https://www.dwd.d

The 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.

## Weather maps

You can also retrieve weather maps from the DWD GeoServer with this package.

## Installation

```python
Expand All @@ -30,6 +36,9 @@ python3 -m pip install simple_dwd_weatherforecast

## Usage

### Weather data

#### Usage example
```python
from simple_dwd_weatherforecast import dwdforecast
from datetime import datetime, timedelta, timezone
Expand All @@ -44,7 +53,7 @@ time_tomorrow = datetime.now(timezone.utc)+timedelta(days=1)
temperature_tomorrow = dwd_weather.get_forecast_data(dwdforecast.WeatherDataType.TEMPERATURE, time_tomorrow)
```

### Available methods
#### Available methods

All 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.

Expand Down Expand Up @@ -106,10 +115,9 @@ class Weather:
get_timeframe_condition(datetime, timeframe: hours after datetime as int, optional bool shouldUpdate) # Result is an approximate "feeled" condition at this time frame

get_weather_report(optional bool shouldUpdate) # Returns the weather report for the geographical region of the station as HTML

```

### Advanced Usage
#### Advanced Usage

If 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:

Expand All @@ -123,6 +131,49 @@ access_forecast_dict = dwd_weather.forecast_data # dwd_weather.forecast_data con

Keep 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&v=4).

## Weather maps

You 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.

![example picture of a map produced with this package](/map_example.png?raw=true "Example picture of a map produced with this package")

#### Usage example
```python
from simple_dwd_weatherforecast import dwdmap

dwdmap.get_from_location(51.272, 8.84, radius_km=100, map_type=dwdmap.WeatherMapType.NIEDERSCHLAGSRADAR, background_type=dwdmap.WeatherBackgroundMapType.BUNDESLAENDER, file_name="map.png")

dwdmap.get_germany(map_type=dwdmap.WeatherMapType.UVINDEX, width=520, height=580, filename="germany.png")
```

#### Available methods

```python

class WeatherMapType(Enum):
NIEDERSCHLAGSRADAR = "dwd:Niederschlagsradar"
MAXTEMP = "dwd:GefuehlteTempMax"
UVINDEX = "dwd:UVI_CS"
POLLENFLUG = "dwd:Pollenflug"
SATELLITE_RGB = "dwd:Satellite_meteosat_1km_euat_rgb_day_hrv_and_night_ir108_3h"
SATELLITE_IR = "dwd:Satellite_worldmosaic_3km_world_ir108_3h"
WARNUNGEN_GEMEINDEN = "dwd:Warnungen_Gemeinden"
WARNUNGEN_KREISE = "dwd:Warnungen_Landkreise"

class WeatherBackgroundMapType(Enum):
LAENDER = "dwd:Laender"
BUNDESLAENDER = "dwd:Warngebiete_Bundeslaender"
KREISE = "dwd:Warngebiete_Kreise"
GEMEINDEN = "dwd:Warngebiete_Gemeinden"
SATELLIT = "dwd:bluemarble"

def get_from_location(longitude, latitude, radius_km, map_type: WeatherMapType, background_type: WeatherBackgroundMapType, optional integer image_width, optional integer image_height, optional string filename (default:"map.png")) #Saves map with given radius from coordinates

get_germany(map_type: WeatherMapType, optional integer image_width, optional integer image_height, optional string filename (default:"map.png")) #Saves map of whole germany

get_map(minx,miny,maxx,maxy, map_type: WeatherMapType, background_type: WeatherBackgroundMapType, optional integer image_width, optional integer image_height, optional string filename (default:"map.png")) #Map retrieval
```

## Help and Contribution

Feel 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.
Expand Down
Binary file added map_example.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

setuptools.setup(
name="simple_dwd_weatherforecast",
version="1.1.5",
version="2.0.0",
author="Max Fermor",
description="A simple tool to retrieve a weather forecast from DWD OpenData",
long_description=long_description,
Expand Down
46 changes: 46 additions & 0 deletions simple_dwd_weatherforecast/dwdmap.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import requests
import math
from io import BytesIO
from PIL import Image
from enum import Enum


class WeatherMapType(Enum):
NIEDERSCHLAGSRADAR = "dwd:Niederschlagsradar"
MAXTEMP = "dwd:GefuehlteTempMax"
UVINDEX = "dwd:UVI_CS"
POLLENFLUG = "dwd:Pollenflug"
SATELLITE_RGB = "dwd:Satellite_meteosat_1km_euat_rgb_day_hrv_and_night_ir108_3h"
SATELLITE_IR = "dwd:Satellite_worldmosaic_3km_world_ir108_3h"
WARNUNGEN_GEMEINDEN = "dwd:Warnungen_Gemeinden"
WARNUNGEN_KREISE = "dwd:Warnungen_Landkreise"

class WeatherBackgroundMapType(Enum):
LAENDER = "dwd:Laender"
BUNDESLAENDER = "dwd:Warngebiete_Bundeslaender"
KREISE = "dwd:Warngebiete_Kreise"
GEMEINDEN = "dwd:Warngebiete_Gemeinden"
SATELLIT = "dwd:bluemarble"

def get_from_location(longitude, latitude, radius_km, map_type: WeatherMapType, background_type: WeatherBackgroundMapType = WeatherBackgroundMapType.BUNDESLAENDER, image_width=520, image_height=580, filename="map.png"):
if radius_km <= 0:
raise ValueError("Radius must be greater than 0")
if latitude < -90 or latitude > 90:
raise ValueError("Latitude must be between -90 and 90")
if longitude < -180 or longitude > 180:
raise ValueError("Longitude must be between -180 and 180")
radius = math.fabs(radius_km / (111.3 * math.cos(latitude)))
get_map(latitude-radius, longitude-radius, latitude+radius, longitude+radius, map_type, background_type, image_width, image_height, filename)

def get_germany(map_type: WeatherMapType, image_width=520, image_height=580, filename="map.png"):
get_map(4.4, 46.4, 16.1, 55.6, map_type, WeatherBackgroundMapType.BUNDESLAENDER, image_width, image_height, filename)

def get_map(minx,miny,maxx,maxy, map_type: WeatherMapType, background_type: WeatherBackgroundMapType, image_width=520, image_height=580, filename="map.png"):
if image_width > 1200 or image_height > 1400:
raise ValueError("Width and height must not exceed 1200 and 1400 respectively. Please be kind to the DWD servers.")

url = f"https://maps.dwd.de/geoserver/dwd/wms?service=WMS&version=1.1.0&request=GetMap&layers={map_type.value},{background_type.value}&bbox={minx},{miny},{maxx},{maxy}&width={image_width}&height={image_height}&srs=EPSG:4326&styles=&format=image/png"
request = requests.get(url, stream=True)
if request.status_code == 200:
image = Image.open(BytesIO(request.content))
image.save(filename)
16 changes: 16 additions & 0 deletions tests/test_map.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import unittest
from simple_dwd_weatherforecast import dwdmap


class MapTestCase(unittest.TestCase):
def test_prevent_too_large_height(self):
with self.assertRaises(ValueError):
dwdmap.get_map(4.4, 46.4, 16.1, 55.6, dwdmap.WeatherMapType.NIEDERSCHLAGSRADAR, dwdmap.WeatherBackgroundMapType.BUNDESLAENDER, 1300, 700)

def test_prevent_too_large_width(self):
with self.assertRaises(ValueError):
dwdmap.get_map(4.4, 46.4, 16.1, 55.6, dwdmap.WeatherMapType.NIEDERSCHLAGSRADAR, dwdmap.WeatherBackgroundMapType.BUNDESLAENDER, 300, 1700)

def test_prevent_too_large(self):
with self.assertRaises(ValueError):
dwdmap.get_map(4.4, 46.4, 16.1, 55.6, dwdmap.WeatherMapType.NIEDERSCHLAGSRADAR, dwdmap.WeatherBackgroundMapType.BUNDESLAENDER, 1300, 1700)

0 comments on commit f7464f5

Please sign in to comment.