aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSam Chudnick <sam@chudnick.com>2023-06-03 13:25:12 -0400
committerSam Chudnick <sam@chudnick.com>2023-06-03 13:25:12 -0400
commit18483e38bbdf92be7f2e33bf5dbb1c8ffc59f018 (patch)
treeb13e9ef6e4d05cd468ca7b59a5102ffeece04bd4
initial commit
-rw-r--r--library.py351
-rw-r--r--main.py53
2 files changed, 404 insertions, 0 deletions
diff --git a/library.py b/library.py
new file mode 100644
index 0000000..ff5735e
--- /dev/null
+++ b/library.py
@@ -0,0 +1,351 @@
1#!/usr/bin/env python3
2import datetime, requests, json, pytz
3from geopy.geocoders import Nominatim, GeoNames
4
5def get_lat_long(location):
6 # Converts a location into latitude and longitude
7 geolocator = Nominatim(user_agent="pywttr")
8 location = geolocator.geocode(location)
9 return location.latitude, location.longitude
10
11def get_time_zone(latitude, longitude):
12 #TODO
13 pass
14
15
16def get_grid_data(latitude, longitude):
17 # Returns id,x,y for a given latitude and longitude
18 grid_data = json.loads(requests.get(f"https://api.weather.gov/points/{latitude},{longitude}").text)
19 grid_id = grid_data["properties"]["gridId"]
20 grid_x = grid_data["properties"]["gridX"]
21 grid_y = grid_data["properties"]["gridY"]
22 return {"grid_id": grid_id, "grid_x": grid_x, "grid_y":grid_y}
23
24
25def get_raw_data(grid_id, grid_x, grid_y):
26 raw_data = json.loads(requests.get(f"https://api.weather.gov/gridpoints/{grid_id}/{grid_x},{grid_y}").text)
27 return raw_data
28
29
30def get_raw_forecast(grid_id, grid_x, grid_y):
31 raw_data = json.loads(requests.get(f"https://api.weather.gov/gridpoints/{grid_id}/{grid_x},{grid_y}/forecast", user_agent="my-test-app").text)
32 return raw_data
33
34
35def get_current_rounded_time():
36 tz = pytz.timezone("America/New_York") #temp
37 cur_time = datetime.datetime.now(tz=tz)
38 cur_time_rounded = cur_time.replace(second=0, microsecond=0, minute=0, hour=cur_time.hour) + datetime.timedelta(hours=cur_time.minute//30)
39 return cur_time_rounded
40
41
42def set_timezone(values):
43 # Takes a list of weather data values
44 # and converts all times to proper timezone
45
46 ret = []
47 tz = pytz.timezone("America/New_York") #temp
48 for val in values:
49 val["time"] = val["time"].astimezone(tz)
50 ret.append(val)
51 return ret
52
53
54def make_current(values):
55 # Takes a list of weather data values
56 # and removes items from before the current time
57 # (to the nearest hour)
58 ret = []
59 cur_time_rounded = get_current_rounded_time()
60 for val in values:
61 if val["time"] >= cur_time_rounded:
62 ret.append(val)
63 return ret
64
65
66def fill_gaps(values):
67 # Takes a list of weather data values
68 # and fills gaps left by duration periods of longer
69 # than 1 hour
70 ret = []
71 for val in values:
72 ret.append(val)
73 duration_hours = int((val["duration"].seconds / 3600) + (val["duration"].days * 24))
74 if duration_hours > 1:
75 for _ in range(0, duration_hours - 1):
76 copy = val.copy()
77 copy["time"] = val["time"] + datetime.timedelta(hours=1)
78 copy["duration"] = datetime.timedelta(hours=1)
79 ret.append(copy)
80 return ret
81
82
83def normalize(values):
84 values = set_timezone(values)
85 values = make_current(values)
86 values = fill_gaps(values)
87 return values
88
89
90def celcius_to_fahrenheit(celcius):
91 fahrenheit = int(celcius * 9/5 + 32)
92 return fahrenheit
93
94
95def parse_duration(duration_str):
96 #Parses time duration string and returns timedelta
97
98 duration_str = duration_str[1:] # strip off leading P
99 period_str, time_str = duration_str.split('T')
100 if len(period_str) > 0:
101 days = int(period_str[0])
102 else:
103 days = 0
104 hours = int(time_str[0:len(time_str)-1])
105 delta = datetime.timedelta(hours=hours, days=days)
106 return delta
107
108
109def get_daily_highs(raw_data):
110 daily_highs_raw = raw_data["properties"]["maxTemperature"]["values"]
111
112 daily_highs = []
113
114 for high in daily_highs_raw:
115 high_celc = high["value"]
116 high_fahr = celcius_to_fahrenheit(high_celc)
117
118 time_str, duration_str = high["validTime"].split('/')
119 time = datetime.datetime.strptime(time_str, "%Y-%m-%dT%H:%M:%S%z")
120 duration = parse_duration(duration_str)
121 daily_highs.append({"time":time,"duration":duration,"high_celc":high_celc,"high_fahr":high_fahr})
122
123 return daily_highs
124
125
126def get_daily_lows(raw_data):
127 daily_lows_raw = raw_data["properties"]["minTemperature"]["values"]
128
129 daily_lows = []
130
131 for low in daily_lows_raw:
132 low_celc = low["value"]
133 low_fahr = celcius_to_fahrenheit(low_celc)
134 time_str, duration_str = low["validTime"].split('/')
135 time = datetime.datetime.strptime(time_str, "%Y-%m-%dT%H:%M:%S%z")
136 duration = parse_duration(duration_str)
137 daily_lows.append({"time":time,"duration":duration,"low_celc":low_celc,"low_fahr":low_far})
138
139 return daily_lows
140
141
142def get_temperature(raw_data):
143 raw_values = raw_data["properties"]["temperature"]["values"]
144 ret = []
145
146 for val in raw_values:
147 val_celc = val["value"]
148 val_fahr = celcius_to_fahrenheit(val_celc)
149 time_str, duration_str = val["validTime"].split('/')
150 time = datetime.datetime.strptime(time_str, "%Y-%m-%dT%H:%M:%S%z")
151 duration = parse_duration(duration_str)
152 ret.append({"time":time,"duration":duration,"value_celc":val_celc,"value_fahr":val_fahr})
153
154 return normalize(ret)
155
156
157def get_apparent_temperature(raw_data):
158 raw_values = raw_data["properties"]["apparentTemperature"]["values"]
159 ret = []
160
161
162 for val in raw_values:
163 val_celc = val["value"]
164 val_fahr = celcius_to_fahrenheit(val_celc)
165 time_str, duration_str = val["validTime"].split('/')
166 time = datetime.datetime.strptime(time_str, "%Y-%m-%dT%H:%M:%S%z")
167 duration = parse_duration(duration_str)
168 ret.append({"time":time,"duration":duration,"value_celc":val_celc,"value_fahr":val_far})
169
170 return normalize(ret)
171
172
173def get_humidity(raw_data):
174 raw_values = raw_data["properties"]["relativeHumidity"]["values"]
175 ret = []
176
177 for val in raw_values:
178 time_str, duration_str = val["validTime"].split('/')
179 time = datetime.datetime.strptime(time_str, "%Y-%m-%dT%H:%M:%S%z")
180 duration = parse_duration(duration_str)
181 ret.append({"time":time,"duration":duration,"value":val["value"]})
182
183 return normalize(ret)
184
185
186def get_wind_chill(raw_data):
187 raw_values = raw_data["properties"]["windChill"]["values"]
188 ret = []
189
190 for val in raw_values:
191 val_celc = val["value"]
192 val_fahr = celcius_to_fahrenheit(val_celc)
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_celc":val_celc,"value_fahr":val_fahr})
197
198 return normalize(ret)
199
200
201def get_wind_speed(raw_data):
202 raw_values = raw_data["properties"]["windSpeed"]["values"]
203 ret = []
204
205 for val in raw_values:
206 time_str, duration_str = val["validTime"].split('/')
207 time = datetime.datetime.strptime(time_str, "%Y-%m-%dT%H:%M:%S%z")
208 duration = parse_duration(duration_str)
209 ret.append({"time":time,"duration":duration,"value":val["value"]})
210
211 return normalize(ret)
212
213
214def get_wind_gust(raw_data):
215 raw_values = raw_data["properties"]["windGust"]["values"]
216 ret = []
217
218 for val in raw_values:
219 time_str, duration_str = val["validTime"].split('/')
220 time = datetime.datetime.strptime(time_str, "%Y-%m-%dT%H:%M:%S%z")
221 duration = parse_duration(duration_str)
222 ret.append({"time":time,"duration":duration,"value":val["value"]})
223
224 return normalize(ret)
225
226
227def get_precip_chance(raw_data):
228 raw_values = raw_data["properties"]["probabilityOfPrecipitation"]["values"]
229 ret = []
230
231 for val in raw_values:
232 time_str, duration_str = val["validTime"].split('/')
233 time = datetime.datetime.strptime(time_str, "%Y-%m-%dT%H:%M:%S%z")
234 duration = parse_duration(duration_str)
235 ret.append({"time":time,"duration":duration,"value":val["value"]})
236
237 return normalize(ret)
238
239
240def get_precip_amount(raw_data):
241 raw_values = raw_data["properties"]["quantitativePrecipitation"]["values"]
242 ret = []
243
244 for val in raw_values:
245 time_str, duration_str = val["validTime"].split('/')
246 time = datetime.datetime.strptime(time_str, "%Y-%m-%dT%H:%M:%S%z")
247 duration = parse_duration(duration_str)
248 ret.append({"time":time,"duration":duration,"value":val["value"]})
249
250 return normalize(ret)
251
252
253def get_snowfall_amount(raw_data):
254 raw_values = raw_data["properties"]["snowfallAmount"]["values"]
255 ret = []
256
257 for val in raw_values:
258 time_str, duration_str = val["validTime"].split('/')
259 time = datetime.datetime.strptime(time_str, "%Y-%m-%dT%H:%M:%S%z")
260 duration = parse_duration(duration_str)
261 ret.append({"time":time,"duration":duration,"value":val["value"]})
262
263 return normalize(ret)
264
265
266def get_snow_level(raw_data):
267 raw_values = raw_data["properties"]["snowLevel"]["values"]
268 ret = []
269
270 for val in raw_values:
271 time_str, duration_str = val["validTime"].split('/')
272 time = datetime.datetime.strptime(time_str, "%Y-%m-%dT%H:%M:%S%z")
273 duration = parse_duration(duration_str)
274 ret.append({"time":time,"duration":duration,"value":val["value"]})
275
276 return normalize(ret)
277
278
279def get_visibility(raw_data):
280 raw_values = raw_data["properties"]["visibility"]["values"]
281 ret = []
282
283 for val in raw_values:
284 time_str, duration_str = val["validTime"].split('/')
285 time = datetime.datetime.strptime(time_str, "%Y-%m-%dT%H:%M:%S%z")
286 duration = parse_duration(duration_str)
287 ret.append({"time":time,"duration":duration,"value":val["value"]})
288
289 return normalize(ret)
290
291
292def get_wind_direction(raw_data):
293 raw_values = raw_data["properties"]["windDirection"]["values"]
294 ret = []
295
296 for val in raw_values:
297 time_str, duration_str = val["validTime"].split('/')
298 time = datetime.datetime.strptime(time_str, "%Y-%m-%dT%H:%M:%S%z")
299 duration = parse_duration(duration_str)
300
301 def degrees_to_cardinal(d):
302 dirs = ['N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW']
303 ix = round(d / (360. / len(dirs)))
304 return dirs[ix % len(dirs)]
305
306 direction_str = degrees_to_cardinal(val["value"])
307
308 ret.append({"time":time,"duration":duration,"value":direction_str})
309
310 return normalize(ret)
311
312
313def get_dewpoint(raw_data):
314 raw_values = raw_data["properties"]["dewpoint"]["values"]
315 ret = []
316
317 for val in raw_values:
318 val_celc = val["value"]
319 val_fahr = celcius_to_fahrenheit(val_celc)
320 time_str, duration_str = val["validTime"].split('/')
321 time = datetime.datetime.strptime(time_str, "%Y-%m-%dT%H:%M:%S%z")
322 duration = parse_duration(duration_str)
323 ret.append({"time":time,"duration":duration,"value_celc":val_celc,"value_fahr":val_fahr})
324
325 return normalize(ret)
326
327
328def get_hourly_data(raw_data, end_time):
329 temps = get_temperature(raw_data)
330 precip_chance = get_precip_chance(raw_data)
331 precip_amount = get_precip_amount(raw_data)
332 humidity = get_humidity(raw_data)
333 wind_speed = get_wind_speed(raw_data)
334 wind_direction = get_wind_direction(raw_data)
335
336 ret = []
337 i = 0
338 while i < len(temps) and temps[i]["time"] < end_time:
339 val_dict = { "time": temps[i]["time"],
340 "temp": int(temps[i]["value_fahr"]),
341 "humidity": int(humidity[i]["value"]),
342 "precip_chance": int(precip_chance[i]["value"]),
343 "precip_amount": int(precip_amount[i]["value"]),
344 "wind_speed": int(wind_speed[i]["value"]),
345 "wind_direction": wind_direction[i]["value"] }
346 ret.append(val_dict)
347 i+=1
348
349 return ret
350
351
diff --git a/main.py b/main.py
new file mode 100644
index 0000000..3fbf3e7
--- /dev/null
+++ b/main.py
@@ -0,0 +1,53 @@
1#!/usr/bin/env python3
2import json, requests, datetime, argparse, pytz
3
4import library
5
6def parse_args():
7 parser = argparse.ArgumentParser()
8 parser.add_argument('-d', '--days', type=int)
9 parser.add_argument('-H', '--hours', type=int)
10 parser.add_argument('-l', '--location', type=str, required=True)
11 return parser.parse_args()
12
13
14def hourly_forecast(args, raw_data):
15
16 init_time = library.get_current_rounded_time()
17 if args.hours is not None:
18 delta = datetime.timedelta(hours=args.hours)
19 elif args.days is not None:
20 delta = datetime.timedelta(days=args.days)
21 else:
22 delta = datetime.timedelta(hours=(24-init_time.hour))
23 end_time = init_time + delta
24
25 hourly_data = library.get_hourly_data(raw_data, end_time)
26
27 for point in hourly_data:
28 print(point["time"].strftime("%a %x %I:%M %p"))
29 print(f'\t Temperature - { point["temp"] }°F')
30 print(f'\t Humidity - { point["humidity"] }%')
31 print(f'\t Chance of Precipitation - { point["precip_chance"] }%')
32 print(f'\t Precipitation Amount - { point["precip_amount"] } in')
33 print(f'\t Wind Speed - { point["wind_speed"] } MPH')
34 print(f'\t Wind Direction - { point["wind_direction"] }')
35 print('\n')
36
37
38
39def daily_forecast(raw_data):
40 pass
41
42
43def main():
44 args = parse_args()
45 latitude, longitude = library.get_lat_long(args.location)
46 grid_data = library.get_grid_data(latitude, longitude)
47 raw_data = library.get_raw_data(grid_data["grid_id"], grid_data["grid_x"], grid_data["grid_y"])
48 hourly_forecast(args, raw_data)
49
50
51
52if __name__ == '__main__':
53 main()