3 from datetime import datetime
6 from typing import Dict, List
7 import urllib.request, urllib.error, urllib.parse
15 class weather_renderer(renderer.debuggable_abstaining_renderer):
16 """A renderer to fetch forecast from wunderground."""
18 def __init__(self, name_to_timeout_dict: Dict[str, int], file_prefix: str) -> None:
19 super(weather_renderer, self).__init__(name_to_timeout_dict, False)
20 self.file_prefix = file_prefix
22 def debug_prefix(self) -> str:
23 return f"weather({self.file_prefix})"
25 def periodic_render(self, key: str) -> bool:
26 return self.fetch_weather()
28 def describe_time(self, index: int) -> str:
38 def describe_wind(self, mph: float) -> str:
50 def describe_magnitude(self, mm: float) -> str:
58 def describe_precip(self, rain: float, snow: float) -> str:
59 if rain == 0.0 and snow == 0.0:
60 return "no precipitation"
61 magnitude = rain + snow
62 if rain > 0 and snow > 0:
63 return f"a {self.describe_magnitude(magnitude)} mix of rain and snow"
65 return f"{self.describe_magnitude(magnitude)} rain"
67 return f"{self.describe_magnitude(magnitude)} snow"
69 def fix_caps(self, s: str) -> str:
72 for x in s.split("."):
74 r += x.capitalize() + ". "
75 r = r.replace(". .", ".")
79 self, conditions: List[str], rain: List[float], snow: List[float]
81 # rain snow clouds sun
85 # mostlycloudy.gif F F 6+ X
86 # partlycloudy.gif F F 4+ 4-
88 # partlysunny.gif F F X 5+
89 # mostlysunny.gif F F X 6+
92 # flurries.gif F T X X (<1")
93 # snow.gif F T X X (else)
101 count = min(len(conditions), len(rain), len(snow))
102 for x in range(0, count):
103 seen_rain = rain[x] > 0
104 seen_snow = snow[x] > 0
105 total_snow += snow[x]
106 txt = conditions[x].lower()
109 if "clear" in txt or "sun" in txt:
112 if seen_rain and seen_snow:
119 return "flurries.gif"
125 return "mostlycloudy.gif"
126 elif cloud_count >= 4:
127 return "partlycloudy.gif"
130 elif clear_count >= 6:
131 return "mostlysunny.gif"
132 elif clear_count >= 4:
133 return "partlysunny.gif"
136 def describe_weather(
141 conditions: List[str],
147 # -onight------ -morning----- -afternoon-- -evening----
148 # 12a-3a 3a-6a 6a-9a 9a-12p 12p-3p 3p-6p 6p-9p 9p-12p
149 # Wind: [12.1 3.06 3.47 4.12 3.69 3.31 2.73 2.1]
150 # Conditions: [Clouds Clouds Clouds Clouds Clouds Clouds Clear Clear]
151 # Rain: [0.4 0.2 0 0 0 0 0 0]
152 # Snow: [0 0 0 0 0 0 0 0]
155 count = min(len(wind), len(conditions), len(rain), len(snow))
162 for x in range(0, count):
163 time = self.describe_time(x)
173 if txt != lcondition:
174 if txt != "Snow" and txt != "Rain":
179 txt = self.describe_wind(wind[x])
183 current += txt + " winds"
187 txt = self.describe_precip(rain[x], snow[x])
200 if random.randint(0, 3) == 0:
201 if time != "overnight":
202 descr += current + " in the " + time + ". "
203 descr += current + " overnight. "
205 if time != "overnight":
207 descr += time + ", " + current + ". "
209 current = current.replace("cloudy", "clouds")
210 descr += current + " developing. "
212 if ltime == "overnight" or ltime == "morning":
213 descr += "Conditions continuing the rest of the day. "
214 descr = descr.replace("with breezy winds", "and breezy")
215 descr = descr.replace("Clear developing", "Skies clearing")
216 descr = self.fix_caps(descr)
219 def fetch_weather(self) -> None:
220 if self.file_prefix == "stevens":
221 text_location = "Stevens Pass, WA"
222 param = "lat=47.74&lon=-121.08"
223 elif self.file_prefix == "telma":
224 text_location = "Telma, WA"
225 param = "lat=47.84&lon=-120.81"
227 text_location = "Bellevue, WA"
230 www = urllib.request.urlopen(
231 "http://api.openweathermap.org/data/2.5/forecast?%s&APPID=%s&units=imperial"
232 % (param, secrets.openweather_key)
234 response = www.read()
236 parsed_json = json.loads(response)
238 # https://openweathermap.org/forecast5
244 # "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},
246 # {"id":800,"main":"Clear","description":"clear sky","icon":"02n"}
248 # "clouds":{"all":8},
249 # "wind":{"speed":4.77,"deg":232.505},
252 # "dt_txt":"2017-01-30 18:00:00"
254 # {"dt":1485810000,....
255 with file_writer.file_writer("weather-%s_3_10800.html" % self.file_prefix) as f:
258 <h1>Weather at {text_location}:</h1>
261 <table width=99% cellspacing=10 border=0>
264 count = parsed_json["cnt"]
273 for x in range(0, count):
274 data = parsed_json["list"][x]
275 dt = data["dt_txt"] # 2019-10-07 18:00:00
276 date = dt.split(" ")[0]
277 time = dt.split(" ")[1]
279 conditions[date] = []
286 for x in range(0, count):
287 data = parsed_json["list"][x]
288 dt = data["dt_txt"] # 2019-10-07 18:00:00
289 date = dt.split(" ")[0]
290 time = dt.split(" ")[1]
294 temp = data["main"]["temp"]
295 if highs[date] < temp:
297 if temp < lows[date]:
299 wind[date].append(data["wind"]["speed"])
300 conditions[date].append(data["weather"][0]["main"])
301 if "rain" in data and "3h" in data["rain"]:
302 rain[date].append(data["rain"]["3h"])
305 if "snow" in data and "3h" in data["snow"]:
306 snow[date].append(data["snow"]["3h"])
310 # {u'clouds': {u'all': 0},
311 # u'sys': {u'pod': u'd'},
312 # u'dt_txt': u'2019-10-09 21:00:00',
314 # {u'main': u'Clear',
317 # u'description': u'clear sky'}
323 # u'grnd_level': 1018.95,
324 # u'temp_max': 54.74,
325 # u'sea_level': 1026.46,
327 # u'pressure': 1026.46,
330 # u'wind': {u'speed': 6.31, u'deg': 10.09}}
333 # for x in xrange(0, 5):
334 # fcast = parsed_json['forecast']['txt_forecast']['forecastday'][x]
335 # text = fcast['fcttext']
336 # text = re.subn(r' ([0-9]+)F', r' \1°F', text)[0]
337 # f.write('<td style="vertical-align:top;font-size:75%%"><P STYLE="padding:8px;">%s</P></td>' % text)
338 # f.write('</tr></table>')
342 # f.write("<table border=0 cellspacing=10>\n")
344 for date in sorted(highs.keys()):
345 today = datetime.fromtimestamp(ts[date])
346 formatted_date = today.strftime("%a %e %b")
347 if formatted_date in days_seen:
349 days_seen[formatted_date] = True
350 num_days = len(list(days_seen.keys()))
353 for date in sorted(highs.keys()):
360 today = datetime.fromtimestamp(ts[date])
361 formatted_date = today.strftime("%a %e %b")
362 if formatted_date in days_seen:
364 days_seen[formatted_date] = True
366 '<td width=%d%% style="vertical-align:top;">\n' % (100 / num_days)
368 f.write("<table border=0>\n")
372 " <tr><td colspan=3 height=50><b><center><font size=6>"
374 + "</font></center></b></td></tr>\n"
379 ' <tr><td colspan=3 height=100><center><img src="/icons/weather/%s" height=125></center></td></tr>\n'
380 % self.pick_icon(conditions[date], rain[date], snow[date])
385 if lows[date] <= 32.5:
388 ' <tr><td width=33%% align=left><font color="%s"><b>%d°F </b></font></td>\n'
389 % (color, int(lows[date]))
396 ' <td width=33%%><center><b><font style="background-color:#dfdfff; color:#003355">%3.1f"</font></b></center></td>\n'
400 f.write(" <td width=33%> </td>\n")
404 if highs[date] >= 80:
407 ' <td align=right><font color="%s"><b> %d°F</b></font></td></tr>\n'
408 % (color, int(highs[date]))
413 '<tr><td colspan=3 style="vertical-align:top;font-size:75%%">%s</td></tr>\n'
414 % self.describe_weather(
423 f.write("</table>\n</td>\n")
424 f.write("</tr></table></center>")
428 # x = weather_renderer({"Stevens": 1000}, "stevens")
429 # x.periodic_render("Stevens")