simple-dwd-weatherforecast 2.1.6__py3-none-any.whl → 2.1.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- simple_dwd_weatherforecast/dwdmap.py +145 -4
- {simple_dwd_weatherforecast-2.1.6.dist-info → simple_dwd_weatherforecast-2.1.8.dist-info}/METADATA +21 -7
- {simple_dwd_weatherforecast-2.1.6.dist-info → simple_dwd_weatherforecast-2.1.8.dist-info}/RECORD +6 -6
- {simple_dwd_weatherforecast-2.1.6.dist-info → simple_dwd_weatherforecast-2.1.8.dist-info}/LICENCE +0 -0
- {simple_dwd_weatherforecast-2.1.6.dist-info → simple_dwd_weatherforecast-2.1.8.dist-info}/WHEEL +0 -0
- {simple_dwd_weatherforecast-2.1.6.dist-info → simple_dwd_weatherforecast-2.1.8.dist-info}/top_level.txt +0 -0
@@ -2,7 +2,7 @@ from typing import Iterable
|
|
2
2
|
import requests
|
3
3
|
import math
|
4
4
|
from io import BytesIO
|
5
|
-
from PIL import Image, ImageFile
|
5
|
+
from PIL import Image, ImageFile, ImageDraw
|
6
6
|
from enum import Enum
|
7
7
|
from collections import deque
|
8
8
|
from datetime import datetime, timedelta, timezone
|
@@ -35,6 +35,51 @@ class germany_boundaries:
|
|
35
35
|
maxy = 55.6
|
36
36
|
|
37
37
|
|
38
|
+
class MarkerShape(Enum):
|
39
|
+
CIRCLE = "circle"
|
40
|
+
SQUARE = "square"
|
41
|
+
CROSS = "cross"
|
42
|
+
|
43
|
+
|
44
|
+
class Marker:
|
45
|
+
def __init__(
|
46
|
+
self,
|
47
|
+
latitude: float,
|
48
|
+
longitude: float,
|
49
|
+
shape: MarkerShape,
|
50
|
+
size: int,
|
51
|
+
colorRGB: tuple[int, int, int],
|
52
|
+
width: int = 0,
|
53
|
+
):
|
54
|
+
if (
|
55
|
+
latitude is None
|
56
|
+
or longitude is None
|
57
|
+
or shape is None
|
58
|
+
or size is None
|
59
|
+
or colorRGB is None
|
60
|
+
):
|
61
|
+
raise ValueError("All values have to be defined")
|
62
|
+
self.latitude = latitude
|
63
|
+
self.longitude = longitude
|
64
|
+
self.shape = shape
|
65
|
+
self.size = size
|
66
|
+
self.colorRGB = colorRGB
|
67
|
+
self.width = width
|
68
|
+
|
69
|
+
|
70
|
+
class ImageBoundaries:
|
71
|
+
minX: float
|
72
|
+
maxX: float
|
73
|
+
minY: float
|
74
|
+
maxY: float
|
75
|
+
|
76
|
+
def __init__(self, minX: float, maxX: float, minY: float, maxY: float) -> None:
|
77
|
+
self.minX = minX
|
78
|
+
self.maxX = maxX
|
79
|
+
self.minY = minY
|
80
|
+
self.maxY = maxY
|
81
|
+
|
82
|
+
|
38
83
|
def get_from_location(
|
39
84
|
longitude,
|
40
85
|
latitude,
|
@@ -43,6 +88,7 @@ def get_from_location(
|
|
43
88
|
background_type: WeatherBackgroundMapType = WeatherBackgroundMapType.BUNDESLAENDER,
|
44
89
|
image_width=520,
|
45
90
|
image_height=580,
|
91
|
+
markers: list[Marker] = [],
|
46
92
|
):
|
47
93
|
if radius_km <= 0:
|
48
94
|
raise ValueError("Radius must be greater than 0")
|
@@ -60,6 +106,7 @@ def get_from_location(
|
|
60
106
|
background_type,
|
61
107
|
image_width,
|
62
108
|
image_height,
|
109
|
+
markers,
|
63
110
|
)
|
64
111
|
|
65
112
|
|
@@ -68,6 +115,7 @@ def get_germany(
|
|
68
115
|
background_type: WeatherBackgroundMapType = WeatherBackgroundMapType.BUNDESLAENDER,
|
69
116
|
image_width=520,
|
70
117
|
image_height=580,
|
118
|
+
markers: list[Marker] = [],
|
71
119
|
):
|
72
120
|
return get_map(
|
73
121
|
germany_boundaries.minx,
|
@@ -78,6 +126,7 @@ def get_germany(
|
|
78
126
|
background_type,
|
79
127
|
image_width,
|
80
128
|
image_height,
|
129
|
+
markers,
|
81
130
|
)
|
82
131
|
|
83
132
|
|
@@ -90,6 +139,7 @@ def get_map(
|
|
90
139
|
background_type: WeatherBackgroundMapType,
|
91
140
|
image_width=520,
|
92
141
|
image_height=580,
|
142
|
+
markers: list[Marker] = [],
|
93
143
|
):
|
94
144
|
if image_width > 1200 or image_height > 1400:
|
95
145
|
raise ValueError(
|
@@ -107,6 +157,7 @@ def get_map(
|
|
107
157
|
request = requests.get(url, stream=True)
|
108
158
|
if request.status_code == 200:
|
109
159
|
image = Image.open(BytesIO(request.content))
|
160
|
+
image = draw_marker(image, ImageBoundaries(minx, maxx, miny, maxy), markers)
|
110
161
|
return image
|
111
162
|
|
112
163
|
|
@@ -134,6 +185,7 @@ class ImageLoop:
|
|
134
185
|
steps: int = 6,
|
135
186
|
image_width: int = 520,
|
136
187
|
image_height: int = 580,
|
188
|
+
markers: list[Marker] = [],
|
137
189
|
):
|
138
190
|
if image_width > 1200 or image_height > 1400:
|
139
191
|
raise ValueError(
|
@@ -150,7 +202,7 @@ class ImageLoop:
|
|
150
202
|
self._steps = steps
|
151
203
|
self._image_width = image_width
|
152
204
|
self._image_height = image_height
|
153
|
-
|
205
|
+
self.markers = markers
|
154
206
|
self._images = deque([], steps)
|
155
207
|
|
156
208
|
self._full_reload()
|
@@ -182,7 +234,10 @@ class ImageLoop:
|
|
182
234
|
self._last_update += timedelta(minutes=5)
|
183
235
|
self._images.append(self._get_image(self._last_update))
|
184
236
|
|
185
|
-
def _get_image(
|
237
|
+
def _get_image(
|
238
|
+
self,
|
239
|
+
date: datetime,
|
240
|
+
) -> ImageFile.ImageFile:
|
186
241
|
if self._background_type in [
|
187
242
|
WeatherBackgroundMapType.SATELLIT,
|
188
243
|
WeatherBackgroundMapType.KREISE,
|
@@ -195,9 +250,95 @@ class ImageLoop:
|
|
195
250
|
request = requests.get(url, stream=True)
|
196
251
|
if request.status_code != 200:
|
197
252
|
raise ConnectionError("Error during image request from DWD servers")
|
198
|
-
|
253
|
+
image = Image.open(BytesIO(request.content))
|
254
|
+
image = draw_marker(
|
255
|
+
image,
|
256
|
+
ImageBoundaries(self._minx, self._maxx, self._miny, self._maxy),
|
257
|
+
self.markers,
|
258
|
+
)
|
259
|
+
return image
|
199
260
|
|
200
261
|
|
201
262
|
def get_time_last_5_min(date: datetime) -> datetime:
|
202
263
|
minute = math.floor(date.minute / 5) * 5
|
203
264
|
return date.replace(minute=minute, second=0, microsecond=0)
|
265
|
+
|
266
|
+
|
267
|
+
def draw_marker(
|
268
|
+
image: ImageFile.ImageFile,
|
269
|
+
image_bounderies: ImageBoundaries,
|
270
|
+
marker_list: list[Marker],
|
271
|
+
):
|
272
|
+
draw = ImageDraw.ImageDraw(image)
|
273
|
+
for marker in marker_list:
|
274
|
+
if (
|
275
|
+
marker.longitude < image_bounderies.minX
|
276
|
+
or marker.longitude > image_bounderies.maxX
|
277
|
+
or marker.latitude < image_bounderies.minY
|
278
|
+
or marker.latitude > image_bounderies.maxY
|
279
|
+
):
|
280
|
+
raise ValueError("Marker location out of boundaries")
|
281
|
+
location_relative_to_image = (
|
282
|
+
(
|
283
|
+
(marker.longitude - image_bounderies.minX)
|
284
|
+
/ (image_bounderies.maxX - image_bounderies.minX)
|
285
|
+
)
|
286
|
+
* image.width,
|
287
|
+
(
|
288
|
+
(image_bounderies.minY - marker.latitude)
|
289
|
+
/ (image_bounderies.maxY - image_bounderies.minY)
|
290
|
+
)
|
291
|
+
* image.height,
|
292
|
+
)
|
293
|
+
if marker.shape == MarkerShape.CIRCLE:
|
294
|
+
draw.circle(
|
295
|
+
location_relative_to_image,
|
296
|
+
round(marker.size / 2, 0),
|
297
|
+
fill=marker.colorRGB,
|
298
|
+
)
|
299
|
+
elif marker.shape == MarkerShape.CROSS:
|
300
|
+
size = round(marker.size / 2, 0)
|
301
|
+
draw.line(
|
302
|
+
[
|
303
|
+
(
|
304
|
+
location_relative_to_image[0] - size,
|
305
|
+
location_relative_to_image[1],
|
306
|
+
),
|
307
|
+
(
|
308
|
+
location_relative_to_image[0] + size,
|
309
|
+
location_relative_to_image[1],
|
310
|
+
),
|
311
|
+
],
|
312
|
+
marker.colorRGB,
|
313
|
+
marker.width,
|
314
|
+
)
|
315
|
+
draw.line(
|
316
|
+
[
|
317
|
+
(
|
318
|
+
location_relative_to_image[0],
|
319
|
+
location_relative_to_image[1] - size,
|
320
|
+
),
|
321
|
+
(
|
322
|
+
location_relative_to_image[0],
|
323
|
+
location_relative_to_image[1] + size,
|
324
|
+
),
|
325
|
+
],
|
326
|
+
marker.colorRGB,
|
327
|
+
marker.width,
|
328
|
+
)
|
329
|
+
elif marker.shape == MarkerShape.SQUARE:
|
330
|
+
size = round(marker.size / 2, 0)
|
331
|
+
draw.rectangle(
|
332
|
+
[
|
333
|
+
(
|
334
|
+
location_relative_to_image[0] - size,
|
335
|
+
location_relative_to_image[1] - size,
|
336
|
+
),
|
337
|
+
(
|
338
|
+
location_relative_to_image[0] + size,
|
339
|
+
location_relative_to_image[1] + size,
|
340
|
+
),
|
341
|
+
],
|
342
|
+
marker.colorRGB,
|
343
|
+
)
|
344
|
+
return image
|
{simple_dwd_weatherforecast-2.1.6.dist-info → simple_dwd_weatherforecast-2.1.8.dist-info}/METADATA
RENAMED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: simple_dwd_weatherforecast
|
3
|
-
Version: 2.1.
|
3
|
+
Version: 2.1.8
|
4
4
|
Summary: A simple tool to retrieve a weather forecast from DWD OpenData
|
5
5
|
Home-page: https://github.com/FL550/simple_dwd_weatherforecast.git
|
6
6
|
Author: Max Fermor
|
@@ -204,11 +204,25 @@ class WeatherBackgroundMapType(Enum):
|
|
204
204
|
GEMEINDEN = "dwd:Warngebiete_Gemeinden"
|
205
205
|
SATELLIT = "dwd:bluemarble"
|
206
206
|
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
207
|
+
class MarkerShape(Enum):
|
208
|
+
CIRCLE = "circle"
|
209
|
+
SQUARE = "square"
|
210
|
+
CROSS = "cross"
|
211
|
+
|
212
|
+
class Marker(
|
213
|
+
latitude: float,
|
214
|
+
longitude: float,
|
215
|
+
shape: MarkerShape,
|
216
|
+
size: int,
|
217
|
+
colorRGB: tuple[int, int, int],
|
218
|
+
width: int = 0,
|
219
|
+
)
|
220
|
+
|
221
|
+
get_from_location(longitude, latitude, radius_km, map_type: WeatherMapType, background_type: WeatherBackgroundMapType, optional integer image_width, optional integer image_height, optional markers: list[Marker]) #Returns map as pillow image with given radius from coordinates
|
222
|
+
|
223
|
+
get_germany(map_type: WeatherMapType, optional WeatherBackgroundMapType background_type, optional integer image_width, optional integer image_height, optional markers: list[Marker]) #Returns map as pillow image of whole germany
|
224
|
+
|
225
|
+
get_map(minx,miny,maxx,maxy, map_type: WeatherMapType, background_type: WeatherBackgroundMapType, optional integer image_width, optional integer image_height, optional markers: list[Marker]) #Returns map as pillow image
|
212
226
|
```
|
213
227
|
|
214
228
|
|
@@ -240,7 +254,7 @@ for image in enumerate(maploop._images):
|
|
240
254
|
|
241
255
|
```python
|
242
256
|
ImageLoop(minx: float, miny: float, maxx: float, maxy: float, map_type: WeatherMapType, background_type: WeatherBackgroundMapType,
|
243
|
-
steps: int = 6, image_width: int = 520,image_height: int = 580) -> ImageLoop
|
257
|
+
steps: int = 6, image_width: int = 520,image_height: int = 580, markers: list[Marker] = []) -> ImageLoop
|
244
258
|
|
245
259
|
get_images() -> Iterable[ImageFile.ImageFile] # Returns the image loop
|
246
260
|
|
{simple_dwd_weatherforecast-2.1.6.dist-info → simple_dwd_weatherforecast-2.1.8.dist-info}/RECORD
RENAMED
@@ -1,6 +1,6 @@
|
|
1
1
|
simple_dwd_weatherforecast/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
2
|
simple_dwd_weatherforecast/dwdforecast.py,sha256=wYg7XC9_5rb-ITdjoT-bLaBc_AGqii8d6eNEfdbfXtY,38863
|
3
|
-
simple_dwd_weatherforecast/dwdmap.py,sha256=
|
3
|
+
simple_dwd_weatherforecast/dwdmap.py,sha256=9zYUrE0j-07Gt6Fo3yyRiCqo2VL7QN_x10IptPfdiio,10932
|
4
4
|
simple_dwd_weatherforecast/stations.json,sha256=1u8qc2CT_rVy49SAlOicGixzHln6Y0FXevuFAz2maBw,838948
|
5
5
|
simple_dwd_weatherforecast/uv_stations.json,sha256=ADenYo-aR6qbf0UFkfYr72kkFzL9HyUKe4VQ23POGF8,2292
|
6
6
|
tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -35,8 +35,8 @@ tests/test_update.py,sha256=AIzzHMxcjwQjeTB0l3YFgB7HkGDbuqiHofwy41mS0m4,7440
|
|
35
35
|
tests/test_update_hourly.py,sha256=7Zl8ml3FTdqw3_Qwr_Tz-sWTzypvrBWmxeig2Vwp_ZQ,1781
|
36
36
|
tests/test_uv_index.py,sha256=tr6wnOyHlXT1S3yp1oeHc4-Brmc-EMEdM4mtyrdpcHg,579
|
37
37
|
tests/test_weather.py,sha256=ZyX4ldUoJpJp7YpiNQwU6Od-nYRay-3qcaDJdNq8fhY,780
|
38
|
-
simple_dwd_weatherforecast-2.1.
|
39
|
-
simple_dwd_weatherforecast-2.1.
|
40
|
-
simple_dwd_weatherforecast-2.1.
|
41
|
-
simple_dwd_weatherforecast-2.1.
|
42
|
-
simple_dwd_weatherforecast-2.1.
|
38
|
+
simple_dwd_weatherforecast-2.1.8.dist-info/LICENCE,sha256=27UG7gteqvSWuZlsbIq2_OAbh7VyifGGl-1zpuUoBcw,1072
|
39
|
+
simple_dwd_weatherforecast-2.1.8.dist-info/METADATA,sha256=3NVZTWgAJ9U4tjotuSGfM8vMOGcqgYMv0oPah1oyLSU,12548
|
40
|
+
simple_dwd_weatherforecast-2.1.8.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
|
41
|
+
simple_dwd_weatherforecast-2.1.8.dist-info/top_level.txt,sha256=iyEobUh14Tzitx39Oi8qm0NhBrnZovl_dNKtvLUkLEM,33
|
42
|
+
simple_dwd_weatherforecast-2.1.8.dist-info/RECORD,,
|
{simple_dwd_weatherforecast-2.1.6.dist-info → simple_dwd_weatherforecast-2.1.8.dist-info}/LICENCE
RENAMED
File without changes
|
{simple_dwd_weatherforecast-2.1.6.dist-info → simple_dwd_weatherforecast-2.1.8.dist-info}/WHEEL
RENAMED
File without changes
|
File without changes
|