epaper/modules/weather.py

371 lines
14 KiB
Python

from datetime import datetime, timezone, timedelta
import io
import math
from PIL import Image, ImageDraw, ImageFont
import pygal
from .weather_api import WeatherAPI
def draw_format_infos(infos, width, height, fnt_R, fnt_B, label_margin, align_height=0, margin_bf_first=True, **kwargs):
image = Image.new('RGBA', (int(width), int(height)))
draw = ImageDraw.Draw(image)
nb_infos = len(infos.keys())
size = 0
for k,v in infos.items():
size += fnt_R.getsize(k)[0]
size += label_margin
size += fnt_B.getsize(v)[0]
margin = (width - size) / nb_infos
align = 0
if margin_bf_first:
align += margin / 2
for k,v in infos.items():
draw.text(
(align, align_height),
k,
font=fnt_R, **kwargs
)
align += fnt_R.getsize(k)[0] + label_margin
draw.text(
(align, align_height),
v,
font=fnt_B, **kwargs
)
align += fnt_B.getsize(v)[0] + margin
return image
def display_longtext(draw, pos, text, font, anchor="lt", maxwidth=9999, **kwargs):
x,y = pos
lines = []
mainlines = text.split("\n")
for line in mainlines:
words = line.split(" ")
cur = ""
while len(words) > 0:
added = (" " if len(cur) > 0 else "") + words[0]
width = font.getsize(cur + added)[0]
if width < maxwidth:
words.pop(0)
cur += added
else:
lines.append(cur)
cur = words.pop(0)
if len(cur) > 0:
lines.append(cur)
line_height = font.getsize("test")[1]
if anchor[1] == "m":
y -= line_height * len(lines) / 2
elif anchor[1] == "b":
y -= line_height * len(lines)
for line in lines:
draw.text((x,y), line, font=font, anchor=anchor, **kwargs)
y += line_height
return line_height * len(lines)
class WeatherToolbarModule:
def __init__(self):
self.label_margin = 3
def draw_module(self, config, width, height):
image = Image.new('RGB', (width, height), 'black')
draw = ImageDraw.Draw(image)
fnt_R = ImageFont.truetype(config.fnt_R_path, 16)
fnt_B = ImageFont.truetype(config.fnt_RB_path, 20)
weather = WeatherAPI().get_currently()
infos = {
"Vent": "%d km/h" % weather["windSpeed"],
"Humidité": "%d%%" % (weather["humidity"] * 100),
"Indice UV": str(weather["uvIndex"]),
"Pression": "%d hPa" % weather["pressure"],
}
txt = draw_format_infos(infos, width, height, fnt_R, fnt_B, self.label_margin, align_height=height/2, anchor="lm")
image.paste(txt, (0,0), txt)
return image
class WeatherJumboCurrentModule:
def __init__(self):
self.middle_align = 1/3
def draw_module(self, config, width, height):
image = Image.new('RGB', (width, height), 'white')
draw = ImageDraw.Draw(image)
fnt_Big = ImageFont.truetype(config.fnt_RB_path, 33)
fnt_R = ImageFont.truetype(config.fnt_R_path, 16)
fnt_B = ImageFont.truetype(config.fnt_RB_path, 16)
# current
curweather = WeatherAPI().get_currently()
icon = Image.open("icons/" + WeatherAPI.get_icon(curweather["icon"], current=True)).resize((height, height))
image.paste(icon, (int(width*self.middle_align - height), 0), icon)
draw.text(
(width*self.middle_align, height/3),
"%d˚ %s." % (math.trunc(curweather["temperature"]), curweather["summary"]),
fill="black", anchor="lb", font=fnt_Big
)
thisdayweather = WeatherAPI().get_daily()["data"][0]
infos = {
"Ressentie": "%d˚" % curweather["apparentTemperature"],
"Minimale": "%d˚" % thisdayweather["temperatureLow"],
"Maximale": "%d˚" % thisdayweather["temperatureHigh"],
}
txt = draw_format_infos(infos, (1-self.middle_align) * width, 20, fnt_R, fnt_B, 5, margin_bf_first=False, fill="black", anchor="lt")
image.paste(txt, (int(self.middle_align*width),int(height/2.6)), txt)
# day
fnt_Rig = ImageFont.truetype(config.fnt_R_path, 20)
dayweather = WeatherAPI().get_hourly()
display_longtext(draw, (width*self.middle_align, height/1.28), dayweather["summary"], fill="black", anchor="lm", font=fnt_Rig, maxwidth=(1-self.middle_align)*width)
return image
class WeatherSunMoonModule:
def draw_module(self, config, width, height):
image = Image.new('RGB', (width, height), 'white')
draw = ImageDraw.Draw(image)
fnt_R = ImageFont.truetype(config.fnt_R_path, 15)
icon = Image.open("icons/" + WeatherAPI().get_moon_icon()).resize((height, height))
image.paste(icon, (0,0), icon)
thisdayweather = WeatherAPI().get_daily()["data"][0]
import time
infos = {
"sunrise": WeatherAPI().read_timestamp(thisdayweather["sunriseTime"]).strftime("%X"),
"sunset": WeatherAPI().read_timestamp(thisdayweather["sunsetTime"]).strftime("%X"),
"4": "",
}
i = 0
line_size = int(height/len(infos.keys()))
for icon, info in infos.items():
if info == "":
continue
icon = Image.open("icons/wi-" + icon + ".png").resize((line_size, line_size))
image.paste(icon, (height,i * line_size), icon)
draw.text(
(height + line_size, i * line_size + line_size / 2),
info,
fill="black", anchor="lm", font=fnt_R
)
i += 1
return image
class WeatherRainModule:
def draw_module(self, config, width, height):
thisdayweather = WeatherAPI().get_daily()["data"][0]
gauge = pygal.SolidGauge(half_pie=True, inner_radius=0.70, width=width, height=height*1.55, style=config.pygal_custom_style, show_legend=False, margin_top=-height*0.58, margin_left=1, margin_right=1)
percent_formatter = lambda x: '{:.10g}%'.format(x)
gauge.value_formatter = percent_formatter
if thisdayweather["precipProbability"] == 0 and thisdayweather["uvIndex"] > 4:
gauge.add('Index UV', [{'value': thisdayweather["uvIndex"] * 10}])
icon_path = "wi-hot.png"
else:
gauge.add('Pluie', [{'value': thisdayweather["precipProbability"] * 100 + 1}])
if thisdayweather["precipProbability"] > 0:
icon_path = "wi-umbrella.png"
else:
icon_path = "wi-na.png"
image = Image.open(io.BytesIO(gauge.render_to_png()))
if icon_path:
icon = Image.open("icons/" + icon_path).resize((int(height/1.25), int(height/1.25)))
image.paste(icon, (int(width/2-height/2.5), int(height-height/1.25)), icon)
return image
class WeatherTemperatureModule:
def __init__(self):
self.limit_futur = 30
def draw_module(self, config, width, height):
thisdayweather = WeatherAPI().get_daily()["data"][0]
line_chart = pygal.Line(interpolate='cubic', width=width, height=height, inverse_y_axis=False, x_label_rotation=45, secondary_range=(0,100) if thisdayweather["precipProbability"] > 0 else (0,10), **config.charts_opts)
line_chart.value_formatter = lambda x: "%d" % x
hours_weather = WeatherAPI().get_hourly()
line_chart.x_labels = [WeatherAPI().read_timestamp(d["time"]).strftime("%Hh") if datetime.fromtimestamp(d["time"]).hour % 2 == 0 else "" for d in hours_weather["data"][:self.limit_futur]]
line_chart.add('Températures', [d["temperature"] for d in hours_weather["data"][:self.limit_futur]], show_dots=False)
if thisdayweather["precipProbability"] > 0:
line_chart.add('Précipitations', [d["precipProbability"] * 100 for d in hours_weather["data"][:self.limit_futur]], secondary=True, show_dots=False, fill=True)
else:
line_chart.add('Index UV', [d["uvIndex"] for d in hours_weather["data"][:self.limit_futur]], secondary=True, show_dots=False)
return Image.open(io.BytesIO(line_chart.render_to_png()))
class WeeklyWeatherModule:
def __init__(self):
self.first_day = 1
self.limit_futur = 7
def draw_module(self, config, width, height):
image = Image.new('RGB', (width, height), 'white')
draw = ImageDraw.Draw(image)
fnt_R = ImageFont.truetype(config.fnt_R_path, 14)
fnt_B = ImageFont.truetype(config.fnt_RB_path, 14)
weekweather = WeatherAPI().get_daily()
daysweather = weekweather["data"]
nbdays = len(daysweather[self.first_day:self.first_day+self.limit_futur])
day_size = int(height / (nbdays + 1))
display_longtext(draw,
(day_size + (width - day_size)/2, day_size / 2 + 5),
weekweather["summary"],
fill="black", anchor="mm", font=fnt_B,
maxwidth=width - day_size
)
temp = []
for day in daysweather[self.first_day:self.first_day+self.limit_futur]:
temp.append(day["temperatureLow"])
temp.append(day["temperatureHigh"])
t_min = min(temp)
t_max = max(temp)
t_scale = (width - day_size - 30) / (t_max - t_min)
i = 1
for day in daysweather[self.first_day:self.first_day+self.limit_futur]:
icon = Image.open("icons/" + WeatherAPI.get_icon(day["icon"])).resize((day_size, day_size))
image.paste(icon, (0, i * day_size), icon)
draw.text(
(15 + 2 + day_size + int((day["temperatureLow"]-t_min)*t_scale), i*day_size + 4),
"%d˚" % math.trunc(day["temperatureLow"]),
fill="black", anchor="rt", font=fnt_R
)
summary_size = fnt_R.getsize(WeatherAPI().read_timestamp(day["time"]).strftime("%a") + " : " + day["summary"])[0]
draw.text(
(day_size + (width - day_size - summary_size) / 2, (i + 1) * day_size - 6),
WeatherAPI().read_timestamp(day["time"]).strftime("%a") + " : ",
fill="#666", anchor="ls", font=fnt_R
)
draw.text(
(day_size + (width - day_size + summary_size) / 2, (i + 1) * day_size - 6),
day["summary"],
fill="black", anchor="rs", font=fnt_R
)
draw.text(
(day_size + int((day["temperatureHigh"]-t_min)*t_scale) + 2, i * day_size + 4),
"%d˚" % math.trunc(day["temperatureHigh"]),
fill="black", anchor="lt", font=fnt_R
)
try:
draw.rounded_rectangle(
(15 + day_size + int((day["temperatureLow"]-t_min)*t_scale),i*day_size + 4,day_size + int((day["temperatureHigh"]-t_min)*t_scale),i*day_size + 14),
radius=5, fill="black")
except AttributeError:
draw.rectangle(
(15 + day_size + int((day["temperatureLow"]-t_min)*t_scale),i*day_size + 4,day_size + int((day["temperatureHigh"]-t_min)*t_scale),i*day_size + 14),
fill="black")
i += 1
return image
class WeatherAlertsModule:
def __init__(self):
self.icon_size = 50
def draw_module(self, config, width, height):
image = Image.new('RGBA', (width, height), "#000")
draw = ImageDraw.Draw(image)
align = 0
if WeatherAPI().has_alerts():
alerts = WeatherAPI().get_alerts()
fnt_R = ImageFont.truetype(config.fnt_R_path, 16)
fnt_B = ImageFont.truetype(config.fnt_RB_path, 16)
align = 9
for alert in alerts:
if alert["severity"] == "watch" or alert["title"].startswith("Moderate"):
icon = "wi-small-craft-advisory.png"
elif alert["severity"] == "warning":
icon = "wi-gale-warning.png"
else:
icon = None
if icon is not None:
color_img = Image.new('RGB', (self.icon_size, self.icon_size), "#fff")
icon_img = Image.open("icons/" + icon).resize((self.icon_size, self.icon_size))
image.paste(color_img, (0, align - 5), icon_img)
draw.text(
(self.icon_size - 5, align),
alert["title"],
fill="white", anchor="lt", font=fnt_B
)
startTime = WeatherAPI().read_timestamp(alert["time"])
endTime = WeatherAPI().read_timestamp(alert["expires"])
# Show alert timing if under a day
if startTime.hour != endTime.hour:
draw.text(
(self.icon_size + fnt_B.getsize(alert["title"])[0], align + 3),
startTime.strftime(("%x " if startTime.day != datetime.now().day else "") + "%X") + " - " + endTime.strftime(("%x " if startTime.day != endTime.day else "") + "%X"),
fill="white", anchor="lt", font=fnt_R
)
elif startTime.day != datetime.now().day:
draw.text(
(self.icon_size + fnt_B.getsize(alert["title"])[0], align + 3),
startTime.strftime("%x"),
fill="white", anchor="lt", font=fnt_R
)
align += fnt_B.getsize(alert["title"])[1]
align += display_longtext(
draw,
(self.icon_size - 5, align),
alert["description"],
fill="white", font=fnt_R,
maxwidth=width-self.icon_size-5
)
align += 7
image = image.crop((0,0,width, align))
return image