Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds configureable mapmarkers #24

Merged
merged 5 commits into from
Oct 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 20 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,11 +184,25 @@ class WeatherBackgroundMapType(Enum):
GEMEINDEN = "dwd:Warngebiete_Gemeinden"
SATELLIT = "dwd:bluemarble"

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

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

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
class MarkerShape(Enum):
CIRCLE = "circle"
SQUARE = "square"
CROSS = "cross"

class Marker(
latitude: float,
longitude: float,
shape: MarkerShape,
size: int,
colorRGB: tuple[int, int, int],
width: int = 0,
)

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

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

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
```


Expand Down Expand Up @@ -220,7 +234,7 @@ for image in enumerate(maploop._images):

```python
ImageLoop(minx: float, miny: float, maxx: float, maxy: float, map_type: WeatherMapType, background_type: WeatherBackgroundMapType,
steps: int = 6, image_width: int = 520,image_height: int = 580) -> ImageLoop
steps: int = 6, image_width: int = 520,image_height: int = 580, markers: list[Marker] = []) -> ImageLoop

get_images() -> Iterable[ImageFile.ImageFile] # Returns the image loop

Expand Down
149 changes: 145 additions & 4 deletions simple_dwd_weatherforecast/dwdmap.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import requests
import math
from io import BytesIO
from PIL import Image, ImageFile
from PIL import Image, ImageFile, ImageDraw
from enum import Enum
from collections import deque
from datetime import datetime, timedelta, timezone
Expand Down Expand Up @@ -35,6 +35,51 @@ class germany_boundaries:
maxy = 55.6


class MarkerShape(Enum):
CIRCLE = "circle"
SQUARE = "square"
CROSS = "cross"


class Marker:
def __init__(
self,
latitude: float,
longitude: float,
shape: MarkerShape,
size: int,
colorRGB: tuple[int, int, int],
width: int = 0,
):
if (
latitude is None
or longitude is None
or shape is None
or size is None
or colorRGB is None
):
raise ValueError("All values have to be defined")
self.latitude = latitude
self.longitude = longitude
self.shape = shape
self.size = size
self.colorRGB = colorRGB
self.width = width


class ImageBoundaries:
minX: float
maxX: float
minY: float
maxY: float

def __init__(self, minX: float, maxX: float, minY: float, maxY: float) -> None:
self.minX = minX
self.maxX = maxX
self.minY = minY
self.maxY = maxY


def get_from_location(
longitude,
latitude,
Expand All @@ -43,6 +88,7 @@ def get_from_location(
background_type: WeatherBackgroundMapType = WeatherBackgroundMapType.BUNDESLAENDER,
image_width=520,
image_height=580,
markers: list[Marker] = [],
):
if radius_km <= 0:
raise ValueError("Radius must be greater than 0")
Expand All @@ -60,6 +106,7 @@ def get_from_location(
background_type,
image_width,
image_height,
markers,
)


Expand All @@ -68,6 +115,7 @@ def get_germany(
background_type: WeatherBackgroundMapType = WeatherBackgroundMapType.BUNDESLAENDER,
image_width=520,
image_height=580,
markers: list[Marker] = [],
):
return get_map(
germany_boundaries.minx,
Expand All @@ -78,6 +126,7 @@ def get_germany(
background_type,
image_width,
image_height,
markers,
)


Expand All @@ -90,6 +139,7 @@ def get_map(
background_type: WeatherBackgroundMapType,
image_width=520,
image_height=580,
markers: list[Marker] = [],
):
if image_width > 1200 or image_height > 1400:
raise ValueError(
Expand All @@ -107,6 +157,7 @@ def get_map(
request = requests.get(url, stream=True)
if request.status_code == 200:
image = Image.open(BytesIO(request.content))
image = draw_marker(image, ImageBoundaries(minx, maxx, miny, maxy), markers)
return image


Expand Down Expand Up @@ -134,6 +185,7 @@ def __init__(
steps: int = 6,
image_width: int = 520,
image_height: int = 580,
markers: list[Marker] = [],
):
if image_width > 1200 or image_height > 1400:
raise ValueError(
Expand All @@ -150,7 +202,7 @@ def __init__(
self._steps = steps
self._image_width = image_width
self._image_height = image_height

self.markers = markers
self._images = deque([], steps)

self._full_reload()
Expand Down Expand Up @@ -182,7 +234,10 @@ def update(self):
self._last_update += timedelta(minutes=5)
self._images.append(self._get_image(self._last_update))

def _get_image(self, date: datetime) -> ImageFile.ImageFile:
def _get_image(
self,
date: datetime,
) -> ImageFile.ImageFile:
if self._background_type in [
WeatherBackgroundMapType.SATELLIT,
WeatherBackgroundMapType.KREISE,
Expand All @@ -195,9 +250,95 @@ def _get_image(self, date: datetime) -> ImageFile.ImageFile:
request = requests.get(url, stream=True)
if request.status_code != 200:
raise ConnectionError("Error during image request from DWD servers")
return Image.open(BytesIO(request.content))
image = Image.open(BytesIO(request.content))
image = draw_marker(
image,
ImageBoundaries(self._minx, self._maxx, self._miny, self._maxy),
self.markers,
)
return image


def get_time_last_5_min(date: datetime) -> datetime:
minute = math.floor(date.minute / 5) * 5
return date.replace(minute=minute, second=0, microsecond=0)


def draw_marker(
image: ImageFile.ImageFile,
image_bounderies: ImageBoundaries,
marker_list: list[Marker],
):
draw = ImageDraw.ImageDraw(image)
for marker in marker_list:
if (
marker.longitude < image_bounderies.minX
or marker.longitude > image_bounderies.maxX
or marker.latitude < image_bounderies.minY
or marker.latitude > image_bounderies.maxY
):
raise ValueError("Marker location out of boundaries")
location_relative_to_image = (
(
(marker.longitude - image_bounderies.minX)
/ (image_bounderies.maxX - image_bounderies.minX)
)
* image.width,
(
(marker.latitude - image_bounderies.minY)
/ (image_bounderies.maxY - image_bounderies.minY)
)
* image.height,
)
if marker.shape == MarkerShape.CIRCLE:
draw.circle(
location_relative_to_image,
round(marker.size / 2, 0),
fill=marker.colorRGB,
)
elif marker.shape == MarkerShape.CROSS:
size = round(marker.size / 2, 0)
draw.line(
[
(
location_relative_to_image[0] - size,
location_relative_to_image[1],
),
(
location_relative_to_image[0] + size,
location_relative_to_image[1],
),
],
marker.colorRGB,
marker.width,
)
draw.line(
[
(
location_relative_to_image[0],
location_relative_to_image[1] - size,
),
(
location_relative_to_image[0],
location_relative_to_image[1] + size,
),
],
marker.colorRGB,
marker.width,
)
elif marker.shape == MarkerShape.SQUARE:
size = round(marker.size / 2, 0)
draw.rectangle(
[
(
location_relative_to_image[0] - size,
location_relative_to_image[1] - size,
),
(
location_relative_to_image[0] + size,
location_relative_to_image[1] + size,
),
],
marker.colorRGB,
)
return image
Loading