-
Notifications
You must be signed in to change notification settings - Fork 1
/
weatherWHAT.py
executable file
·369 lines (294 loc) · 12.9 KB
/
weatherWHAT.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# weather display
# by Sean Danischevsky 2019, 2022
#
# get weather from met office website
# https://www.metoffice.gov.uk/services/data/met-office-weather-datahub
# and display on screen or Pimeroni InkyWhat
#
# this file takes the command line input and gets the forecast data
# then optionally calls
# weatherDisplay for display as an image on InkyWhat or as a saved image
import argparse
import ss_download
# from https://raw.githubusercontent.com/MetOffice/weather_datahub_utilities/main/site_specific_download/ss_download.py
# i replaced print(req.text)
# with return (req.json())
try:
import api
except:
print ("Please register with The Meteorological Office, https://metoffice.apiconnect.ibmcloud.com/metoffice/production/ - enter your email adress and create a password, copy your API key and save it in a file next to this one, called\napi.py")
raise Exception
def valid_date(s):
# parse date inputs from user as string, output datetime object
import datetime
try:
return datetime.datetime.strptime(s, "%Y-%m-%d")
except ValueError:
msg = "Please enter a date in the format 'YYYY-MM-DD'"
raise argparse.ArgumentTypeError(msg)
def load_forecast(loadforecastfile):
#load pickled forecast data
import pickle
return pickle.load(open(loadforecastfile, "rb"))
def save_forecast(forecast, now, local_timezone_name, local_now, saveforecast):
#save forecast data as a pickle file
#input a dir or filepath
#return pickled then unpickled object
import os
import pickle
if os.path.isdir(saveforecast):
#create a filename
saveforecastfile= os.path.join(saveforecast, now.strftime("%Y_%m_%d__%H_%M") + '.pickle')
#print ('saved filename', saveforecastfile)
else:
saveforecastfile= saveforecast
pickle.dump((forecast, now, local_timezone_name, local_now), open(saveforecastfile, "wb"))
return pickle.load(open(saveforecastfile, "rb"))
#run on command line:
def display_weather(
lat= api.lat,
lon= api.lon,
bg_file= None,
bg_map= False,
zoom= False,
show_on_inky= False,
inky_colour= None,
show_image= False,
save_image= None,
loadforecast= None,
saveforecast= None,
old= False,
banner= '',
location_banner='',
verbose= False):
# bg_file: image filepath for background. Can be a directory from which a random image is used.
# bg_map: show location on map
# zoom: show location on zoomed in map
# show_on_inky: display image on Pimeroni InkyWHAT
# show_image: display image on screen
# save_image: filepath to save resulting image
# loadforecast: load forecast data from filepath instead of getting live forecast
# saveforecast: filepath to save the forecast in a pickle file
# old: if True, get historical wather forecast
# banner: message to display
# verbose: if True, display diagnostic info
import met_weather
#load or save pickled forecasts
if loadforecast:
forecast, now, local_timezone_name, local_now= load_forecast(loadforecast)
# TODO: need to set 'now' from forecast object
else:
# get current forecast
now, local_timezone_name, local_now= met_weather.get_now(lon, lat)
forecast = ss_download.retrieve_forecast(ss_download.base_url, "hourly", {"apikey": api.key}, api.lat, api.lon, "FALSE", "TRUE")
if saveforecast:
#save weather and reload it to check
forecast, now, local_timezone_name, local_now= save_forecast(forecast, now, local_timezone_name, local_now, saveforecast)
# Parse data for text display
forecast_elements= {'local_now':local_now}
# next sunrise and sunset times
forecast_elements['sun_msg']= met_weather.get_next_sunrise_or_sunset_msg(now, api.lon, api.lat, local_timezone_name)
features= forecast['features']
timeSeries= features[0]['properties']['timeSeries']
idx= met_weather.get_current_timestamp_index(forecast, now)
# current temperature
screenTemperature= timeSeries[idx]['screenTemperature']
forecast_elements['temperature_msg']= str(round(screenTemperature))+ "°"
# high and low temperatures in the next 24 hours:
forecast_elements['hi_lo_msg']= met_weather.get_high_low_msg(timeSeries[idx:][:24], now, local_timezone_name)
#date
forecast_elements["local_now"]= local_now.strftime("%A %d %b %Y")
#short text forecast summary
# TODO GET FROM RSS FEED
summary= ""
# get soonest alert
#TODO: get alert from RSS feed
alert= None
#switch summary for alert if there is one
if alert:
summary= alert
forecast_elements["summary"]= summary
forecast_elements["forecast_background"]= met_weather.significantWeatherCode[timeSeries[idx]['significantWeatherCode']]
forecast_elements["hours"]= [met_weather.convert_utc_to_local(met_weather.convert_from_iso(t['time']), local_timezone_name).strftime("%H") for t in timeSeries[idx:][:24]]
forecast_elements["uvIndex"]= [t['uvIndex'] for t in timeSeries[idx:][:24]]
forecast_elements["precipitationRate"]= [t['precipitationRate'] for t in timeSeries[idx:][:24]]
forecast_elements["probOfPrecipitation"]= [t['probOfPrecipitation']/100.0 for t in timeSeries[idx:][:24]]
forecast_elements['temperatures']= [str(round(t['screenTemperature'])) for t in timeSeries[idx:][:24]]
#display weather in text only mode (not wrapped/formatted to screen).
if verbose or not (save_image or show_image or show_on_inky):
print ("#"*len(forecast_elements["hours"])*4)
print (banner)
print (location_banner)
print (forecast_elements["local_now"]) #date
print (forecast_elements["forecast_background"])
print (forecast_elements["temperature_msg"])
print (forecast_elements["hi_lo_msg"])
print (forecast_elements["sun_msg"])
print (forecast_elements["summary"])#'Overcast throughout the day.'
if verbose:
print()
print ("Time now:", now)
print (f"Now as {local_timezone_name}: {local_now}")
print ("UV index:")
print (" ".join([str(hour) for hour in forecast_elements["uvIndex"]]))
print()
print ('Precipitation Rate:')
print (" ".join([str(hour) for hour in forecast_elements["precipitationRate"]]))
print ('Precipitation intensity:')
print (" ".join([str(hour) for hour in forecast_elements["probOfPrecipitation"]]))
print ("Hourly Temperature")
print (" ".join(forecast_elements['temperatures']))
print (" ".join([hour for hour in forecast_elements["hours"]]))
if verbose or not (save_image or show_image or show_on_inky):
print ("#"*len(forecast_elements["hours"])*4)
if save_image or show_image or show_on_inky:
import weatherDisplay
weatherDisplay.main(forecast_elements,
lat, lon,
bg_file,
bg_map,
zoom,
show_on_inky,
inky_colour,
show_image,
save_image,
banner,
location_banner,
verbose)
#run on command line:
if __name__ == "__main__":
parser= argparse.ArgumentParser()
#verbose
parser.add_argument(
'-v', '--verbose',
help= 'show diagnostic info.',
action= "store_true")
#Banner
parser.add_argument(
'-b', '--banner',
help= 'banner message at top of screen.',
default= '',
required= False)
#mutually exclusive group: latlong or location
location= parser.add_mutually_exclusive_group()
#latitude, longitude
location.add_argument(
'-ll', '--latlong',
type= (str),
help= "forecast for location as 'latitude, longitude'. Use quotes and a space before negative numbers, e.g. -ll ' -5.55 66' .",
required= False)
#latitude, longitude
location.add_argument(
'-l', '--location',
help= 'forecast for location in plain text, e.g. postode, streetname. Requires https://github.com/geopy/geopy ',
required= False)
# mutually exclusive group: map or zoomed map
bg= parser.add_mutually_exclusive_group()
#World Map Display
bg.add_argument(
'-m', '--map',
help= 'Show world map with forecast location.',
action= "store_true",
required= False)
# world map display
bg.add_argument(
'-z', '--zoom',
help= 'Show zoomed world map with forecast location.',
action= "store_true",
required= False)
#filepath of image to Display. If a folder, choose randomly from that folder.
bg.add_argument(
'-bg', '--bg',
help= 'Filepath of background image to display. If a folder, choose randomly from that folder. If the folders have one of the following values: clear-day, clear-night, rain, snow, sleet, wind, fog, cloudy, partly-cloudy-day, or partly-cloudy-night, the appropriate weather background will be taken from that folder.',
default= None,
required= False)
#InkyWhat
parser.add_argument(
'-i', '--inky',
help= 'Show image on the InkyWhat.',
action= "store_true",
required= False)
parser.add_argument('--type', '-t', type=str, required=False, choices=["what", "phat"], default= "what", help="type of display")
parser.add_argument('--colour', '-c', type=str, required=False, choices=["red", "black", "yellow"], default= "black", help="ePaper display colour")
#Display Image
parser.add_argument(
'-d', '--display',
help= 'Show image on the screen display.',
action= "store_true",
required= False)
#Save Image
parser.add_argument(
'-s', '--save',
help= 'Save image filepath.',
required= False)
#Save pickle of forecast to this filepath
parser.add_argument(
'-sf', '--saveforecast',
help= "save forecast data to filepath, e.g. '/path/weather.pickle'. If filepath is a directory, the filename will be automatically generated and place in that directory.",
required= False)
#mutually exclusive group: load pickle or ask for previous forecast data
forecast_time= parser.add_mutually_exclusive_group()
#Load Pickle instead of getting current forecast
forecast_time.add_argument(
'-lf', '--loadforecast',
help= 'load forecast data from filepath instead of live forecast.',
required= False)
#get forecast for this date and time.
forecast_time.add_argument(
'-o', '--old',
help= "get forecast for this date and time. Use 'YYYY-MM-DD'. You don't need to add hours and minutes.",
type= valid_date,
required= False)
args= parser.parse_args()
location_banner= ""
if args.latlong:
latlong= args.latlong
lat, lon= latlong.split(',')
if args.verbose:
print ('Latitude, Longitude from command arguments:', lat, ",", lon)
elif args.location:
from geopy.geocoders import Nominatim
geolocator= Nominatim(user_agent= api.user_agent)
location= geolocator.geocode(args.location)
try:
if args.verbose:
print (location.raw)
lat, lon= location.latitude, location.longitude
location_banner= location.address
if args.verbose:
print ('Latitude, Longitude from geopy:', lat, ",", lon)
except AttributeError:
print (f"Sorry, Geopy could not find location {args.location}!\nTry searching https://nominatim.openstreetmap.org/\nor look up the latitude and longitude on Google Maps, and enter the latitude and longitude with the '-ll' option.")
exit()
else:
# use default location from secrets file
lat= api.lat
lon= api.lon
if args.verbose:
print ('Latitude, Longitude from secrets file:', lat, ",", lon)
# could be useful for map zoom:
# add variables to zoom to optionally take a box
# location.raw['boundingbox'] returns latitude min, max, longitude min, max:
# ['48.8155755', '48.902156', '2.224122', '2.4697602']
# print ("{}\nLatitude, Longitude:\n{}, {}".format(location.address, location.latitude, location.longitude))
if (args.bg or args.map or args.zoom) and (not args.inky and not args.display and not args.save):
# user has gone to the trouble of specifting an image, but they won't see it!
parser.error("--bg, --map or --zoom requires --display, --save or --inky.")
display_weather(
lat= lat,
lon= lon,
bg_file= args.bg,
bg_map= args.map,
zoom= args.zoom,
show_on_inky= args.inky,
inky_colour= args.colour,
show_image= args.display,
save_image= args.save,
loadforecast= args.loadforecast,
saveforecast= args.saveforecast,
old= args.old,
banner= args.banner,
location_banner= location_banner,
verbose= args.verbose)