#!/usr/bin/env python3
from datetime import datetime
import json
import re
from typing import Dict, List
import urllib.request, urllib.error, urllib.parse
import file_writer
import renderer
import secrets
import random
class weather_renderer(renderer.debuggable_abstaining_renderer):
"""A renderer to fetch forecast from wunderground."""
def __init__(self, name_to_timeout_dict: Dict[str, int], file_prefix: str) -> None:
super(weather_renderer, self).__init__(name_to_timeout_dict, False)
self.file_prefix = file_prefix
def debug_prefix(self) -> str:
return f"weather({self.file_prefix})"
def periodic_render(self, key: str) -> bool:
return self.fetch_weather()
def describe_time(self, index: int) -> str:
if index <= 1:
return "overnight"
elif index <= 3:
return "morning"
elif index <= 5:
return "afternoon"
else:
return "evening"
def describe_wind(self, mph: float) -> str:
if mph <= 0.3:
return "calm"
elif mph <= 5.0:
return "light"
elif mph < 15.0:
return "breezy"
elif mph <= 25.0:
return "gusty"
else:
return "heavy"
def describe_magnitude(self, mm: float) -> str:
if mm < 2.0:
return "light"
elif mm < 10.0:
return "moderate"
else:
return "heavy"
def describe_precip(self, rain: float, snow: float) -> str:
if rain == 0.0 and snow == 0.0:
return "no precipitation"
magnitude = rain + snow
if rain > 0 and snow > 0:
return f"a {self.describe_magnitude(magnitude)} mix of rain and snow"
elif rain > 0:
return f"{self.describe_magnitude(magnitude)} rain"
elif snow > 0:
return f"{self.describe_magnitude(magnitude)} snow"
return "rain"
def fix_caps(self, s: str) -> str:
r = ""
s = s.lower()
for x in s.split("."):
x = x.strip()
r += x.capitalize() + ". "
r = r.replace(". .", ".")
return r
def pick_icon(
self, conditions: List[str], rain: List[float], snow: List[float]
) -> str:
# rain snow clouds sun
# fog.gif
# hazy.gif
# clear.gif
# mostlycloudy.gif F F 6+ X
# partlycloudy.gif F F 4+ 4-
# cloudy.gif
# partlysunny.gif F F X 5+
# mostlysunny.gif F F X 6+
# rain.gif T F X X
# sleet.gif T T X X
# flurries.gif F T X X (<1")
# snow.gif F T X X (else)
# sunny.gif F F X 7+
# tstorms.gif
seen_rain = False
seen_snow = False
cloud_count = 0
clear_count = 0
total_snow = 0.0
count = min(len(conditions), len(rain), len(snow))
for x in range(0, count):
seen_rain = rain[x] > 0
seen_snow = snow[x] > 0
total_snow += snow[x]
txt = conditions[x].lower()
if "cloud" in txt:
cloud_count += 1
if "clear" in txt or "sun" in txt:
clear_count += 1
if seen_rain and seen_snow:
if total_snow < 10:
return "sleet.gif"
else:
return "snow.gif"
if seen_snow:
if total_snow < 10:
return "flurries.gif"
else:
return "snow.gif"
if seen_rain:
return "rain.gif"
if cloud_count >= 6:
return "mostlycloudy.gif"
elif cloud_count >= 4:
return "partlycloudy.gif"
if clear_count >= 7:
return "sunny.gif"
elif clear_count >= 6:
return "mostlysunny.gif"
elif clear_count >= 4:
return "partlysunny.gif"
return "clear.gif"
def describe_weather(
self,
high: float,
low: float,
wind: List[float],
conditions: List[str],
rain: List[float],
snow: List[float],
) -> str:
# High temp: 65
# Low temp: 44
# -onight------ -morning----- -afternoon-- -evening----
# 12a-3a 3a-6a 6a-9a 9a-12p 12p-3p 3p-6p 6p-9p 9p-12p
# Wind: [12.1 3.06 3.47 4.12 3.69 3.31 2.73 2.1]
# Conditions: [Clouds Clouds Clouds Clouds Clouds Clouds Clear Clear]
# Rain: [0.4 0.2 0 0 0 0 0 0]
# Snow: [0 0 0 0 0 0 0 0]
high = int(high)
low = int(low)
count = min(len(wind), len(conditions), len(rain), len(snow))
descr = ""
lcondition = ""
lwind = ""
lprecip = ""
ltime = ""
for x in range(0, count):
time = self.describe_time(x)
current = ""
chunks = 0
txt = conditions[x]
if txt == "Clouds":
txt = "cloudy"
elif txt == "Rain":
txt = "rainy"
if txt != lcondition:
if txt != "Snow" and txt != "Rain":
current += txt
chunks += 1
lcondition = txt
txt = self.describe_wind(wind[x])
if txt != lwind:
if len(current) > 0:
current += " with "
current += txt + " winds"
lwind = txt
chunks += 1
txt = self.describe_precip(rain[x], snow[x])
if txt != lprecip:
if len(current) > 0:
if chunks > 1:
current += " and "
else:
current += " with "
chunks += 1
current += txt
lprecip = txt
if len(current):
if ltime != time:
if random.randint(0, 3) == 0:
if time != "overnight":
descr += current + " in the " + time + ". "
descr += current + " overnight. "
else:
if time != "overnight":
descr += "In the "
descr += time + ", " + current + ". "
else:
current = current.replace("cloudy", "clouds")
descr += current + " developing. "
ltime = time
if ltime == "overnight" or ltime == "morning":
descr += "Conditions continuing the rest of the day. "
descr = descr.replace("with breezy winds", "and breezy")
descr = descr.replace("Clear developing", "Skies clearing")
descr = self.fix_caps(descr)
return descr
def fetch_weather(self) -> bool:
if self.file_prefix == "stevens":
text_location = "Stevens Pass, WA"
param = "lat=47.74&lon=-121.08"
elif self.file_prefix == "telma":
text_location = "Telma, WA"
param = "lat=47.84&lon=-120.81"
else:
text_location = "Bellevue, WA"
param = "id=5786882"
www = urllib.request.urlopen(
"http://api.openweathermap.org/data/2.5/forecast?%s&APPID=%s&units=imperial"
% (param, secrets.openweather_key)
)
response = www.read()
www.close()
parsed_json = json.loads(response)
# https://openweathermap.org/forecast5
# {"cod":"200",
# "message":0.0036,
# "cnt":40,
# "list":[
# {"dt":1485799200,
# "main":{"temp":261.45,"temp_min":259.086,"temp_max":261.45,"pressure":1023.48,"sea_level":1045.39,"grnd_level":1023.48,"humidity":79,"temp_kf":2.37},
# "weather":[
# {"id":800,"main":"Clear","description":"clear sky","icon":"02n"}
# ],
# "clouds":{"all":8},
# "wind":{"speed":4.77,"deg":232.505},
# "snow":{},
# "sys":{"pod":"n"},
# "dt_txt":"2017-01-30 18:00:00"
# },
# {"dt":1485810000,....
with file_writer.file_writer("weather-%s_3_10800.html" % self.file_prefix) as f:
f.write(
f"""
Weather at {text_location}:
"""
)
count = parsed_json["cnt"]
ts = {}
highs = {}
lows = {}
wind: Dict[str, List[float]] = {}
conditions: Dict[str, List[str]] = {}
rain: Dict[str, List[float]] = {}
snow: Dict[str, List[float]] = {}
for x in range(0, count):
data = parsed_json["list"][x]
dt = data["dt_txt"] # 2019-10-07 18:00:00
date = dt.split(" ")[0]
time = dt.split(" ")[1]
wind[date] = []
conditions[date] = []
highs[date] = -99999
lows[date] = +99999
rain[date] = []
snow[date] = []
ts[date] = 0
for x in range(0, count):
data = parsed_json["list"][x]
dt = data["dt_txt"] # 2019-10-07 18:00:00
date = dt.split(" ")[0]
time = dt.split(" ")[1]
_ = data["dt"]
if _ > ts[date]:
ts[date] = _
temp = data["main"]["temp"]
if highs[date] < temp:
highs[date] = temp
if temp < lows[date]:
lows[date] = temp
wind[date].append(data["wind"]["speed"])
conditions[date].append(data["weather"][0]["main"])
if "rain" in data and "3h" in data["rain"]:
rain[date].append(data["rain"]["3h"])
else:
rain[date].append(0)
if "snow" in data and "3h" in data["snow"]:
snow[date].append(data["snow"]["3h"])
else:
snow[date].append(0)
# {u'clouds': {u'all': 0},
# u'sys': {u'pod': u'd'},
# u'dt_txt': u'2019-10-09 21:00:00',
# u'weather': [
# {u'main': u'Clear',
# u'id': 800,
# u'icon': u'01d',
# u'description': u'clear sky'}
# ],
# u'dt': 1570654800,
# u'main': {
# u'temp_kf': 0,
# u'temp': 54.74,
# u'grnd_level': 1018.95,
# u'temp_max': 54.74,
# u'sea_level': 1026.46,
# u'humidity': 37,
# u'pressure': 1026.46,
# u'temp_min': 54.74
# },
# u'wind': {u'speed': 6.31, u'deg': 10.09}}
# Next 5 half-days
# for x in xrange(0, 5):
# fcast = parsed_json['forecast']['txt_forecast']['forecastday'][x]
# text = fcast['fcttext']
# text = re.subn(r' ([0-9]+)F', r' \1°F', text)[0]
# f.write('%s | ' % text)
# f.write('
')
# f.close()
# return True
# f.write("\n")
days_seen = {}
for date in sorted(highs.keys()):
today = datetime.fromtimestamp(ts[date])
formatted_date = today.strftime("%a %e %b")
if formatted_date in days_seen:
continue
days_seen[formatted_date] = True
num_days = len(list(days_seen.keys()))
days_seen = {}
for date in sorted(highs.keys()):
precip = 0.0
for _ in rain[date]:
precip += _
for _ in snow[date]:
precip += _
today = datetime.fromtimestamp(ts[date])
formatted_date = today.strftime("%a %e %b")
if formatted_date in days_seen:
continue
days_seen[formatted_date] = True
f.write(
'\n' % (100 / num_days)
)
f.write("\n")
# Date
f.write(
" "
+ formatted_date
+ " | \n"
)
# Icon
f.write(
' ![](/icons/weather/%s) | \n'
% self.pick_icon(conditions[date], rain[date], snow[date])
)
# Low temp
color = "#000099"
if lows[date] <= 32.5:
color = "#009999"
f.write(
' %d°F | \n'
% (color, int(lows[date]))
)
# Total precip
precip *= 0.0393701
if precip > 0.025:
f.write(
' %3.1f" | \n'
% precip
)
else:
f.write(" | \n")
# High temp
color = "#800000"
if highs[date] >= 80:
color = "#AA0000"
f.write(
' %d°F | \n'
% (color, int(highs[date]))
)
# Text "description"
f.write(
'%s | \n'
% self.describe_weather(
highs[date],
lows[date],
wind[date],
conditions[date],
rain[date],
snow[date],
)
)
f.write(" \n | \n")
f.write("
")
return True
# x = weather_renderer({"Stevens": 1000}, "stevens")
# x.periodic_render("Stevens")