aboutsummaryrefslogtreecommitdiff
path: root/library.py
diff options
context:
space:
mode:
Diffstat (limited to 'library.py')
-rw-r--r--library.py430
1 files changed, 48 insertions, 382 deletions
diff --git a/library.py b/library.py
index 0d490a1..5c17e96 100644
--- a/library.py
+++ b/library.py
@@ -2,59 +2,68 @@
2import datetime, requests, json, pytz, sys 2import datetime, requests, json, pytz, sys
3from geopy.geocoders import Nominatim, GeoNames 3from geopy.geocoders import Nominatim, GeoNames
4 4
5weather_codes = {0:"Clear Sky", 1:"Mainly Clear",2:"Partly Cloudy",3:"Overcast",45:"Fog",48:"Fog",51:"Light Drizzle",\
6 53:"Moderate Drizzle",55:"Dense Drizzle",56:"Light Freezing Drizzle",57:"Freezing Drizzle",\
7 61:"Light Rain",63:"Moderate Rain",65:"Heavy Rain",66:"Light Freezing Rain",67:"Freezing Rain",\
8 71:"Light Snowfall",73:"Moderate Snowfall",75:"Heavy Snowfall",77:"Snow Grains",80:"Slight Rain Showers",\
9 81:"Moderate Rain Showers",82:"Heavy Rain Showers",85:"Slight Snow Showers",86:"Heavy Snow Showers",\
10 95:"Thunderstorm",96:"Strong Thunderstrom",99:"Heavy Thunderstrom"}
11
12weather_icons = {0:('wi-day-sunny','wi-night-clear'), 1:('wi-day-sunny','wi-night-clear'), 2:('wi-cloudy','wi-night-partly-cloudy'),\
13 3:('wi-day-sunny-overcast','wi-night-cloudy'), 45:('wi-day-fog','wi-night-fog'), 48:('wi-day-fog','wi-night-fog'), \
14 51:('wi-day-rain','wi-night-rain'),53:('wi-day-rain','wi-night-rain'),55:('wi-day-rain','wi-night-rain'),\
15 56:('wi-day-rain','wi-night-rain'),57:('wi-day-rain','wi-night-rain'),61:('wi-day-rain','wi-night-rain'),\
16 63:('wi-day-rain','wi-night-rain'),65:('wi-day-rain','wi-night-rain'),66:('wi-day-rain','wi-night-rain'),\
17 67:('wi-day-rain','wi-night-rain'),71:('wi-day-snow','wi-night-snow'),73:('wi-day-snow','wi-night-snow'),\
18 71:('wi-day-snow','wi-night-snow'),75:('wi-day-snow','wi-night-snow'),77:('wi-day-snow','wi-night-snow'),\
19 80:('wi-day-showers','wi-night-showers'),81:('wi-day-showers','wi-night-showers'),82:('wi-day-showers','wi-night-showers'),\
20 85:('wi-day-snow','wi-night-snow'),86:('wi-day-snow','wi-night-snow'),95:('wi-day-storm-showers','wi-night-storm-showers'),\
21 96:('wi-day-storm-showers','wi-night-storm-showers'),99:('wi-day-storm-showers','wi-night-storm-showers')}
22
5def get_lat_long(location): 23def get_lat_long(location):
6 # Converts a location into latitude and longitude 24 # Converts a location into latitude and longitude
7 geolocator = Nominatim(user_agent="pywttr") 25 url = f"https://geocoding-api.open-meteo.com/v1/search?name={location}&count=1&language=en&format=json"
8 location = geolocator.geocode(location) 26 headers = {"User-Agent": "pywttr 0.1"}
9 return location.latitude, location.longitude 27 data = requests.get(url, headers=headers).json()
28 latitude = data["results"][0]["latitude"]
29 longitude = data["results"][0]["longitude"]
30 return latitude, longitude
10 31
11def get_time_zone(latitude, longitude):
12 #TODO
13 pass
14 32
33def get_data(latitude, longitude):
34 headers = {"User-Agent": "pywttr 0.1"}
35 now = datetime.datetime.now()
36 now_str = now.strftime("%Y-%m-%d")
37 end_str = (now + datetime.timedelta(days=7)).strftime("%Y-%m-%d")
15 38
16def get_grid_data(latitude, longitude): 39 url_base = f"https://api.open-meteo.com/v1/forecast?latitude={latitude}&longitude={longitude}"
17 # Returns id,x,y for a given latitude and longitude 40 url_weather_params = "&current_weather=true&hourly=temperature_2m&hourly=relativehumidity_2m&hourly=apparent_temperature&hourly=precipitation_probability&hourly=precipitation&hourly=weathercode&hourly=windspeed_10m&hourly=winddirection_10m&daily=weathercode&daily=temperature_2m_max&daily=temperature_2m_min&daily=sunrise&daily=sunset&daily=precipitation_sum&daily=precipitation_probability_max&temperature_unit=fahrenheit&windspeed_unit=mph&precipitation_unit=inch&timeformat=iso8601&past_days=0&forecast_days=7"
18 grid_data = json.loads(requests.get(f"https://api.weather.gov/points/{latitude},{longitude}").text) 41 url_date_time = f"&start_date={now_str}&end_date={end_str}&timezone=auto"
19 grid_id = grid_data["properties"]["gridId"] 42 url = url_base + url_weather_params + url_date_time
20 grid_x = grid_data["properties"]["gridX"] 43 data = requests.get(url, headers=headers).json()
21 grid_y = grid_data["properties"]["gridY"]
22 return {"grid_id": grid_id, "grid_x": grid_x, "grid_y":grid_y}
23 44
45 for i in range(len(data["hourly"]["time"])):
46 data["hourly"]["time"][i] = datetime.datetime.strptime(data["hourly"]["time"][i], '%Y-%m-%dT%H:%M').strftime('%a %x %I:%M %p')
47 for i in range(len(data["daily"]["time"])):
48 data["daily"]["time"][i] = datetime.datetime.strptime(data["daily"]["time"][i], '%Y-%m-%d').strftime('%a %x')
49 for i in range(len(data["daily"]["sunrise"])):
50 data["daily"]["sunrise"][i] = datetime.datetime.strptime(data["daily"]["sunrise"][i], '%Y-%m-%dT%H:%M').strftime('%I:%M %p')
51 for i in range(len(data["daily"]["sunset"])):
52 data["daily"]["sunset"][i] = datetime.datetime.strptime(data["daily"]["sunset"][i], '%Y-%m-%dT%H:%M').strftime('%I:%M %p')
24 53
25def get_raw_data(grid_id, grid_x, grid_y): 54 data["current_weather"]["time"] = datetime.datetime.strptime(data["current_weather"]["time"], '%Y-%m-%dT%H:%M').strftime('%a %x %I:%M %p')
26 headers = {"User-Agent": "pywttr 0.1"}
27 raw_data = json.loads(requests.get(f"https://api.weather.gov/gridpoints/{grid_id}/{grid_x},{grid_y}", headers=headers).text)
28 return raw_data
29 55
30 56 return data
31def get_raw_forecast(grid_id, grid_x, grid_y):
32 headers = {"User-Agent": "pywttr 0.1"}
33 raw_data = json.loads(requests.get(f"https://api.weather.gov/gridpoints/{grid_id}/{grid_x},{grid_y}/forecast", headers=headers).text)
34 return raw_data
35 57
36 58
37def get_current_rounded_time(): 59def get_current_rounded_time(tz_str):
38 # Gets current time rounded down to the hour 60 # Gets current time rounded down to the hour
39 61 tz = pytz.timezone(tz_str)
40 tz = pytz.timezone("America/New_York") #temp
41 cur_time = datetime.datetime.now(tz=tz) 62 cur_time = datetime.datetime.now(tz=tz)
42 cur_time_rounded = cur_time.replace(second=0, microsecond=0, minute=0, hour=cur_time.hour) 63 cur_time_rounded = cur_time.replace(second=0, microsecond=0, minute=0, hour=cur_time.hour)
43 return cur_time_rounded 64 return cur_time_rounded
44 65
45 66
46def set_timezone(values):
47 # Takes a list of weather data values
48 # and converts all times to proper timezone
49
50 ret = []
51 tz = pytz.timezone("America/New_York") #temp
52 for val in values:
53 val["time"] = val["time"].astimezone(tz)
54 ret.append(val)
55 return ret
56
57
58def make_current(values): 67def make_current(values):
59 # Takes a list of weather data values 68 # Takes a list of weather data values
60 # and removes items from before the current time 69 # and removes items from before the current time
@@ -67,349 +76,6 @@ def make_current(values):
67 return ret 76 return ret
68 77
69 78
70def fill_gaps(values): 79def translate_weather_code(weather_code:int):
71 # Takes a list of weather data values 80 return weather_codes[weather_code]
72 # and fills gaps left by duration periods of longer
73 # than 1 hour
74 ret = []
75 for val in values:
76 ret.append(val)
77 duration_hours = int((val["duration"].seconds / 3600) + (val["duration"].days * 24))
78 if duration_hours > 1:
79 for i in range(1, duration_hours):
80 copy = val.copy()
81 copy["time"] = val["time"] + datetime.timedelta(hours=i)
82 copy["duration"] = datetime.timedelta(hours=1)
83 ret.append(copy)
84 return ret
85
86
87def normalize(values):
88 values = set_timezone(values)
89 values = make_current(values)
90 values = fill_gaps(values)
91 return values
92
93
94def celcius_to_fahrenheit(celcius):
95 fahrenheit = int(celcius * 9/5 + 32)
96 return fahrenheit
97
98
99def parse_duration(duration_str):
100 #Parses time duration string and returns timedelta
101
102 duration_str = duration_str[1:] # strip off leading P
103 period_str, time_str = duration_str.split('T')
104 if len(period_str) > 0:
105 days = int(period_str[0])
106 else:
107 days = 0
108 hours = int(time_str[0:len(time_str)-1])
109 delta = datetime.timedelta(hours=hours, days=days)
110 return delta
111
112
113def get_daily_highs(raw_data):
114 daily_highs_raw = raw_data["properties"]["maxTemperature"]["values"]
115
116 daily_highs = []
117
118 for high in daily_highs_raw:
119 high_celc = high["value"]
120 high_fahr = celcius_to_fahrenheit(high_celc)
121
122 time_str, duration_str = high["validTime"].split('/')
123 time = datetime.datetime.strptime(time_str, "%Y-%m-%dT%H:%M:%S%z")
124 duration = parse_duration(duration_str)
125 daily_highs.append({"time":time,"duration":duration,"high_celc":high_celc,"high_fahr":high_fahr})
126
127 return daily_highs
128
129
130def get_daily_lows(raw_data):
131 daily_lows_raw = raw_data["properties"]["minTemperature"]["values"]
132
133 daily_lows = []
134
135 for low in daily_lows_raw:
136 low_celc = low["value"]
137 low_fahr = celcius_to_fahrenheit(low_celc)
138 time_str, duration_str = low["validTime"].split('/')
139 time = datetime.datetime.strptime(time_str, "%Y-%m-%dT%H:%M:%S%z")
140 duration = parse_duration(duration_str)
141 daily_lows.append({"time":time,"duration":duration,"low_celc":low_celc,"low_fahr":low_fahr})
142
143 return daily_lows
144
145
146def get_daily_forecast(raw_data):
147 daily_forecast_raw = raw_data["properties"]["periods"]
148
149 daily_forecast = []
150 for point in daily_forecast_raw:
151 time_str = point["startTime"]
152 time = datetime.datetime.strptime(time_str, "%Y-%m-%dT%H:%M:%S%z")
153 daily_forecast.append({"time":time, "short_forecast":point["shortForecast"], "detailed_forecast":point["detailedForecast"]})
154 return make_current(set_timezone(daily_forecast))
155
156
157def get_temperature(raw_data):
158 raw_values = raw_data["properties"]["temperature"]["values"]
159 ret = []
160
161 for val in raw_values:
162 val_celc = round(val["value"])
163 val_fahr = round(celcius_to_fahrenheit(val_celc))
164 time_str, duration_str = val["validTime"].split('/')
165 time = datetime.datetime.strptime(time_str, "%Y-%m-%dT%H:%M:%S%z")
166 duration = parse_duration(duration_str)
167 ret.append({"time":time,"duration":duration,"value_celc":val_celc,"value_fahr":val_fahr})
168
169 return normalize(ret)
170
171
172def get_apparent_temperature(raw_data):
173 raw_values = raw_data["properties"]["apparentTemperature"]["values"]
174 ret = []
175
176
177 for val in raw_values:
178 val_celc = round(val["value"])
179 val_fahr = round(celcius_to_fahrenheit(val_celc))
180 time_str, duration_str = val["validTime"].split('/')
181 time = datetime.datetime.strptime(time_str, "%Y-%m-%dT%H:%M:%S%z")
182 duration = parse_duration(duration_str)
183 ret.append({"time":time,"duration":duration,"value_celc":val_celc,"value_fahr":val_far})
184
185 return normalize(ret)
186
187
188def get_humidity(raw_data):
189 raw_values = raw_data["properties"]["relativeHumidity"]["values"]
190 ret = []
191
192 for val in raw_values:
193 time_str, duration_str = val["validTime"].split('/')
194 time = datetime.datetime.strptime(time_str, "%Y-%m-%dT%H:%M:%S%z")
195 duration = parse_duration(duration_str)
196 ret.append({"time":time,"duration":duration,"value":round(val["value"])})
197
198 return normalize(ret)
199
200
201def get_wind_chill(raw_data):
202 raw_values = raw_data["properties"]["windChill"]["values"]
203 ret = []
204
205 for val in raw_values:
206 val_celc = round(val["value"])
207 val_fahr = round(celcius_to_fahrenheit(val_celc))
208 time_str, duration_str = val["validTime"].split('/')
209 time = datetime.datetime.strptime(time_str, "%Y-%m-%dT%H:%M:%S%z")
210 duration = parse_duration(duration_str)
211 ret.append({"time":time,"duration":duration,"value_celc":val_celc,"value_fahr":val_fahr})
212
213 return normalize(ret)
214
215
216def get_wind_speed(raw_data):
217 raw_values = raw_data["properties"]["windSpeed"]["values"]
218 ret = []
219
220 for val in raw_values:
221 time_str, duration_str = val["validTime"].split('/')
222 time = datetime.datetime.strptime(time_str, "%Y-%m-%dT%H:%M:%S%z")
223 duration = parse_duration(duration_str)
224 ret.append({"time":time,"duration":duration,"value":round(val["value"])})
225
226 return normalize(ret)
227
228
229def get_wind_gust(raw_data):
230 raw_values = raw_data["properties"]["windGust"]["values"]
231 ret = []
232
233 for val in raw_values:
234 time_str, duration_str = val["validTime"].split('/')
235 time = datetime.datetime.strptime(time_str, "%Y-%m-%dT%H:%M:%S%z")
236 duration = parse_duration(duration_str)
237 ret.append({"time":time,"duration":duration,"value":round(val["value"])})
238
239 return normalize(ret)
240
241
242def get_precip_chance(raw_data):
243 raw_values = raw_data["properties"]["probabilityOfPrecipitation"]["values"]
244 ret = []
245
246 for val in raw_values:
247 time_str, duration_str = val["validTime"].split('/')
248 time = datetime.datetime.strptime(time_str, "%Y-%m-%dT%H:%M:%S%z")
249 duration = parse_duration(duration_str)
250 ret.append({"time":time,"duration":duration,"value":round(val["value"])})
251
252 return normalize(ret)
253
254
255def get_precip_amount(raw_data):
256 raw_values = raw_data["properties"]["quantitativePrecipitation"]["values"]
257 ret = []
258
259 for val in raw_values:
260 time_str, duration_str = val["validTime"].split('/')
261 time = datetime.datetime.strptime(time_str, "%Y-%m-%dT%H:%M:%S%z")
262 duration = parse_duration(duration_str)
263 ret.append({"time":time,"duration":duration,"value":round(val["value"])})
264
265 return normalize(ret)
266
267
268def get_snowfall_amount(raw_data):
269 raw_values = raw_data["properties"]["snowfallAmount"]["values"]
270 ret = []
271
272 for val in raw_values:
273 time_str, duration_str = val["validTime"].split('/')
274 time = datetime.datetime.strptime(time_str, "%Y-%m-%dT%H:%M:%S%z")
275 duration = parse_duration(duration_str)
276 ret.append({"time":time,"duration":duration,"value":round(val["value"])})
277
278 return normalize(ret)
279
280
281def get_snow_level(raw_data):
282 raw_values = raw_data["properties"]["snowLevel"]["values"]
283 ret = []
284
285 for val in raw_values:
286 time_str, duration_str = val["validTime"].split('/')
287 time = datetime.datetime.strptime(time_str, "%Y-%m-%dT%H:%M:%S%z")
288 duration = parse_duration(duration_str)
289 ret.append({"time":time,"duration":duration,"value":round(val["value"])})
290
291 return normalize(ret)
292
293
294def get_visibility(raw_data):
295 raw_values = raw_data["properties"]["visibility"]["values"]
296 ret = []
297
298 for val in raw_values:
299 time_str, duration_str = val["validTime"].split('/')
300 time = datetime.datetime.strptime(time_str, "%Y-%m-%dT%H:%M:%S%z")
301 duration = parse_duration(duration_str)
302 ret.append({"time":time,"duration":duration,"value":round(val["value"])})
303
304 return normalize(ret)
305
306
307def get_wind_direction(raw_data):
308 raw_values = raw_data["properties"]["windDirection"]["values"]
309 ret = []
310
311 for val in raw_values:
312 time_str, duration_str = val["validTime"].split('/')
313 time = datetime.datetime.strptime(time_str, "%Y-%m-%dT%H:%M:%S%z")
314 duration = parse_duration(duration_str)
315
316 def degrees_to_cardinal(d):
317 dirs = ['N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW']
318 ix = round(d / (360. / len(dirs)))
319 return dirs[ix % len(dirs)]
320
321 direction_str = degrees_to_cardinal(val["value"])
322
323 ret.append({"time":time,"duration":duration,"value":direction_str})
324
325 return normalize(ret)
326
327
328def get_dewpoint(raw_data):
329 raw_values = raw_data["properties"]["dewpoint"]["values"]
330 ret = []
331
332 for val in raw_values:
333 val_celc = round(val["value"])
334 val_fahr = round(celcius_to_fahrenheit(val_celc))
335 time_str, duration_str = val["validTime"].split('/')
336 time = datetime.datetime.strptime(time_str, "%Y-%m-%dT%H:%M:%S%z")
337 duration = parse_duration(duration_str)
338 ret.append({"time":time,"duration":duration,"value_celc":val_celc,"value_fahr":val_fahr})
339
340 return normalize(ret)
341
342
343def get_hourly_data(raw_data, end_time):
344 temps = get_temperature(raw_data)
345 humidity = get_humidity(raw_data)
346 precip_chance = get_precip_chance(raw_data)
347 precip_amount = get_precip_amount(raw_data)
348 wind_speed = get_wind_speed(raw_data)
349 wind_direction = get_wind_direction(raw_data)
350 ret_list = []
351 i = 0
352 while i < len(temps) and temps[i]["time"] < end_time:
353 if i >= len(temps):
354 temps.append({"value":"N/A"})
355 if i >= len(humidity):
356 humidity.append({"value":"N/A"})
357 if i >= len(precip_chance):
358 precip_chance.append({"value":"N/A"})
359 if i >= len(precip_amount):
360 precip_amount.append({"value":"N/A"})
361 if i >= len(wind_speed):
362 wind_speed.append({"value":"N/A"})
363 if i >= len(wind_direction):
364 wind_direction.append({"value":"N/A"})
365
366 val_dict = { "time": temps[i]["time"],
367 "temp": temps[i]["value_fahr"],
368 "humidity": humidity[i]["value"],
369 "precip_chance": precip_chance[i]["value"],
370 "precip_amount": precip_amount[i]["value"],
371 "wind_speed": wind_speed[i]["value"],
372 "wind_direction": wind_direction[i]["value"] }
373 ret_list.append(val_dict)
374 i+=1
375
376 return ret_list
377
378
379def get_daily_data(raw_data, raw_forecast, end_time):
380 daily_highs = get_daily_highs(raw_data)
381 daily_lows = get_daily_lows(raw_data)
382 daily_forecasts = get_daily_forecast(raw_forecast)
383 ret_list = []
384 i = 0
385 while i < len(daily_highs) and daily_highs[i]["time"] < end_time:
386 val_dict = { "time": daily_highs[i]["time"],
387 "high": daily_highs[i]["high_fahr"],
388 "low": daily_lows[i]["low_fahr"],
389 "short_forecast_am": daily_forecasts[i]["short_forecast"],
390 "detailed_forecast_am": daily_forecasts[i]["detailed_forecast"],
391 "short_forecast_pm": daily_forecasts[i+1]["short_forecast"],
392 "detailed_forecast_pm": daily_forecasts[i+1]["detailed_forecast"] }
393 ret_list.append(val_dict)
394 i+=1
395 return ret_list
396
397
398def hourly_forecast(raw_data, days):
399 init_time = get_current_rounded_time()
400 if days > 0:
401 delta = datetime.timedelta(days=days)
402 else:
403 delta = datetime.timedelta(hours=(24-init_time.hour))
404 end_time = init_time + delta
405 return get_hourly_data(raw_data, end_time)
406
407 81
408def daily_forecast(raw_data, raw_forecast, days):
409 init_time = get_current_rounded_time()
410 if days > 0:
411 delta = datetime.timedelta(days=days)
412 else:
413 delta = datetime.timedelta(days=5)
414 end_time = init_time + delta
415 return get_daily_data(raw_data, raw_forecast, end_time)