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.
@@ -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(self, date: datetime) -> ImageFile.ImageFile:
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
- return Image.open(BytesIO(request.content))
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: simple_dwd_weatherforecast
3
- Version: 2.1.6
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
- get_from_location(longitude, latitude, radius_km, map_type: WeatherMapType, background_type: WeatherBackgroundMapType, optional integer image_width, optional integer image_height) #Returns map as pillow image with given radius from coordinates
208
-
209
- get_germany(map_type: WeatherMapType, optional WeatherBackgroundMapType background_type, optional integer image_width, optional integer image_height, optional string save_to_filename) #Returns map as pillow image of whole germany
210
-
211
- get_map(minx,miny,maxx,maxy, map_type: WeatherMapType, background_type: WeatherBackgroundMapType, optional integer image_width, optional integer image_height, optional string save_to_filename) #Returns map as pillow image
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
 
@@ -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=cPCcL1u5qeIEDLBcL0qOH0-7dPIi3mge4-PUqfZlRQc,6737
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.6.dist-info/LICENCE,sha256=27UG7gteqvSWuZlsbIq2_OAbh7VyifGGl-1zpuUoBcw,1072
39
- simple_dwd_weatherforecast-2.1.6.dist-info/METADATA,sha256=F6Wv_Y6m1wrEFJgHlnDzMR-sx3_oUVRb9B0ocsUKhyI,12215
40
- simple_dwd_weatherforecast-2.1.6.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
41
- simple_dwd_weatherforecast-2.1.6.dist-info/top_level.txt,sha256=iyEobUh14Tzitx39Oi8qm0NhBrnZovl_dNKtvLUkLEM,33
42
- simple_dwd_weatherforecast-2.1.6.dist-info/RECORD,,
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,,