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 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, 26) 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"])).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() draw.text( (width*self.middle_align, height/1.6), dayweather["summary"], fill="black", anchor="lt", font=fnt_Rig ) 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": datetime.fromtimestamp(thisdayweather["sunriseTime"], tz=timezone.utc).strftime("%X"), "sunset": datetime.fromtimestamp(thisdayweather["sunsetTime"], tz=timezone.utc).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 = [datetime.fromtimestamp(d["time"], tz=timezone.utc).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, 12) 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)) draw.text( (day_size + 3, day_size / 2), "%s" % (weekweather["summary"]), fill="black", anchor="lm", font=fnt_R ) 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 ) draw.text( (day_size + (width - day_size) / 2, (i + 1) * day_size - 4), day["summary"], fill="black", anchor="mb", 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 = 10 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 = datetime.fromtimestamp(alert["time"], tz=timezone.utc) endTime = datetime.fromtimestamp(alert["expires"], tz=timezone.utc) # 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] draw.multiline_text( (self.icon_size - 5, align), alert["description"], fill="white", font=fnt_R ) align += draw.multiline_textsize( alert["description"], font=fnt_R )[1] image = image.crop((0,0,width, align)) return image