1 from datetime import datetime
7 import urllib.request, urllib.error, urllib.parse
11 class weather_renderer(renderer.debuggable_abstaining_renderer):
12 """A renderer to fetch forecast from wunderground."""
14 def __init__(self, name_to_timeout_dict, file_prefix):
15 super(weather_renderer, self).__init__(name_to_timeout_dict, False)
16 self.file_prefix = file_prefix
18 def debug_prefix(self):
19 return "weather(%s)" % (self.file_prefix)
21 def periodic_render(self, key):
22 return self.fetch_weather()
24 def describe_time(self, index):
34 def describe_wind(self, mph):
46 def describe_magnitude(self, mm):
54 def describe_precip(self, rain, snow):
55 if rain == 0 and snow == 0:
56 return "no precipitation"
57 magnitude = rain + snow
58 if rain > 0 and snow > 0:
59 return "a %s mix of rain and snow" % self.describe_magnitude(magnitude)
61 return "%s rain" % self.describe_magnitude(magnitude)
63 return "%s snow" % self.describe_magnitude(magnitude)
65 def fix_caps(self, s):
68 for x in s.split("."):
70 r += x.capitalize() + ". "
71 r = r.replace(". .", ".")
74 def pick_icon(self, conditions, rain, snow):
75 # rain snow clouds sun
79 # mostlycloudy.gif F F 6+ X
80 # partlycloudy.gif F F 4+ 4-
82 # partlysunny.gif F F X 5+
83 # mostlysunny.gif F F X 6+
86 # flurries.gif F T X X (<1")
87 # snow.gif F T X X (else)
95 count = min(len(conditions), len(rain), len(snow))
96 for x in range(0, count):
97 seen_rain = rain[x] > 0
98 seen_snow = snow[x] > 0
100 txt = conditions[x].lower()
103 if "clear" in txt or "sun" in txt:
106 if seen_rain and seen_snow:
113 return "flurries.gif"
119 return "mostlycloudy.gif"
120 elif cloud_count >= 4:
121 return "partlycloudy.gif"
124 elif clear_count >= 6:
125 return "mostlysunny.gif"
126 elif clear_count >= 4:
127 return "partlysunny.gif"
130 def describe_weather(self, high, low, wind, conditions, rain, snow):
133 # -onight------ -morning----- -afternoon-- -evening----
134 # 12a-3a 3a-6a 6a-9a 9a-12p 12p-3p 3p-6p 6p-9p 9p-12p
135 # Wind: [12.1 3.06 3.47 4.12 3.69 3.31 2.73 2.1]
136 # Conditions: [Clouds Clouds Clouds Clouds Clouds Clouds Clear Clear]
137 # Rain: [0.4 0.2 0 0 0 0 0 0]
138 # Snow: [0 0 0 0 0 0 0 0]
141 count = min(len(wind), len(conditions), len(rain), len(snow))
148 for x in range(0, count):
149 time = self.describe_time(x)
159 if txt != lcondition:
160 if txt != "Snow" and txt != "Rain":
165 txt = self.describe_wind(wind[x])
169 current += txt + " winds"
173 txt = self.describe_precip(rain[x], snow[x])
186 if random.randint(0, 3) == 0:
187 if time != "overnight":
188 descr += current + " in the " + time + ". "
189 descr += current + " overnight. "
191 if time != "overnight":
193 descr += time + ", " + current + ". "
195 current = current.replace("cloudy", "clouds")
196 descr += current + " developing. "
198 if ltime == "overnight" or ltime == "morning":
199 descr += "Conditions continuing the rest of the day. "
200 descr = descr.replace("with breezy winds", "and breezy")
201 descr = descr.replace("Clear developing", "Skies clearing")
202 descr = self.fix_caps(descr)
205 def fetch_weather(self):
206 if self.file_prefix == "stevens":
207 text_location = "Stevens Pass, WA"
208 param = "lat=47.74&lon=-121.08"
209 elif self.file_prefix == "telma":
210 text_location = "Telma, WA"
211 param = "lat=47.84&lon=-120.81"
213 text_location = "Bellevue, WA"
216 www = urllib.request.urlopen(
217 "http://api.openweathermap.org/data/2.5/forecast?%s&APPID=%s&units=imperial"
218 % (param, secrets.openweather_key)
220 response = www.read()
222 parsed_json = json.loads(response)
224 # https://openweathermap.org/forecast5
230 # "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},
232 # {"id":800,"main":"Clear","description":"clear sky","icon":"02n"}
234 # "clouds":{"all":8},
235 # "wind":{"speed":4.77,"deg":232.505},
238 # "dt_txt":"2017-01-30 18:00:00"
240 # {"dt":1485810000,....
241 f = file_writer.file_writer("weather-%s_3_10800.html" % self.file_prefix)
244 <h1>Weather at %s:</h1>
247 <table width=99%% cellspacing=10 border=0>
251 count = parsed_json["cnt"]
260 for x in range(0, count):
261 data = parsed_json["list"][x]
262 dt = data["dt_txt"] # 2019-10-07 18:00:00
263 date = dt.split(" ")[0]
264 time = dt.split(" ")[1]
266 conditions[date] = []
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]
281 temp = data["main"]["temp"]
282 if highs[date] < temp:
284 if temp < lows[date]:
286 wind[date].append(data["wind"]["speed"])
287 conditions[date].append(data["weather"][0]["main"])
288 if "rain" in data and "3h" in data["rain"]:
289 rain[date].append(data["rain"]["3h"])
292 if "snow" in data and "3h" in data["snow"]:
293 snow[date].append(data["snow"]["3h"])
297 # {u'clouds': {u'all': 0},
298 # u'sys': {u'pod': u'd'},
299 # u'dt_txt': u'2019-10-09 21:00:00',
301 # {u'main': u'Clear',
304 # u'description': u'clear sky'}
310 # u'grnd_level': 1018.95,
311 # u'temp_max': 54.74,
312 # u'sea_level': 1026.46,
314 # u'pressure': 1026.46,
317 # u'wind': {u'speed': 6.31, u'deg': 10.09}}
320 # for x in xrange(0, 5):
321 # fcast = parsed_json['forecast']['txt_forecast']['forecastday'][x]
322 # text = fcast['fcttext']
323 # text = re.subn(r' ([0-9]+)F', r' \1°F', text)[0]
324 # f.write('<td style="vertical-align:top;font-size:75%%"><P STYLE="padding:8px;">%s</P></td>' % text)
325 # f.write('</tr></table>')
329 # f.write("<table border=0 cellspacing=10>\n")
331 for date in sorted(highs.keys()):
332 today = datetime.fromtimestamp(ts[date])
333 formatted_date = today.strftime("%a %e %b")
334 if formatted_date in days_seen:
336 days_seen[formatted_date] = True
337 num_days = len(list(days_seen.keys()))
340 for date in sorted(highs.keys()):
347 today = datetime.fromtimestamp(ts[date])
348 formatted_date = today.strftime("%a %e %b")
349 if formatted_date in days_seen:
351 days_seen[formatted_date] = True
352 f.write('<td width=%d%% style="vertical-align:top;">\n' % (100 / num_days))
353 f.write("<table border=0>\n")
357 " <tr><td colspan=3 height=50><b><center><font size=6>"
359 + "</font></center></b></td></tr>\n"
364 ' <tr><td colspan=3 height=100><center><img src="/icons/weather/%s" height=125></center></td></tr>\n'
365 % self.pick_icon(conditions[date], rain[date], snow[date])
370 if lows[date] <= 32.5:
373 ' <tr><td width=33%% align=left><font color="%s"><b>%d°F </b></font></td>\n'
374 % (color, int(lows[date]))
381 ' <td width=33%%><center><b><font style="background-color:#dfdfff; color:#003355">%3.1f"</font></b></center></td>\n'
385 f.write(" <td width=33%> </td>\n")
389 if highs[date] >= 80:
392 ' <td align=right><font color="%s"><b> %d°F</b></font></td></tr>\n'
393 % (color, int(highs[date]))
398 '<tr><td colspan=3 style="vertical-align:top;font-size:75%%">%s</td></tr>\n'
399 % self.describe_weather(
408 f.write("</table>\n</td>\n")
409 f.write("</tr></table></center>")
413 # x = weather_renderer({"Stevens": 1000},
415 # x.periodic_render("Stevens")