Alex Stoken commited on
Commit
ca487d2
·
1 Parent(s): 7f59d8a

add app.py and requirements

Browse files
Files changed (2) hide show
  1. app.py +258 -0
  2. requirements.txt +3 -0
app.py ADDED
@@ -0,0 +1,258 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests
2
+ import pandas as pd
3
+ import datetime
4
+ import time
5
+ import gradio as gr
6
+ import os
7
+
8
+ ###########
9
+ # other API's of interest: https://medium.com/@imdipto/best-free-alternatives-to-the-wunderground-weather-api-21acb22450e6
10
+ ##########
11
+ OPENWEATHER_API_KEY = os.environ.get('OPENWEATHER_API_KEY')
12
+ WEATHERAPI_KEY = os.environ.get('WEATHERAPI_KEY')
13
+
14
+
15
+ def openweather_to_result(lat, lon, gmt_time):
16
+ """
17
+ API docs: https://openweathermap.org/api/one-call-api#current
18
+
19
+ Parameters
20
+ ------------
21
+ lat [float]: decimal valued latitude
22
+ lon [float]: decimal valued longitude
23
+ gmt_time [datetime object]: time of desired forecast, in gmt and as python datetime object
24
+
25
+ Returns
26
+ --------
27
+ cloud_pct Tuple(List, List): list of cloud percent and corresponding time for times within 1.5 hours of input GMT time
28
+ """
29
+ exclude_parts = 'current,minutely,daily,alerts'
30
+ request_url = f'https://api.openweathermap.org/data/2.5/onecall?lat={lat}&lon={lon}&exclude={exclude_parts}&appid={OPENWEATHER_API_KEY}'
31
+
32
+ response = requests.get(request_url)
33
+
34
+ data = response.json()
35
+
36
+ cloud_pct = []
37
+ forecast_times = []
38
+
39
+ # timeframe around input time to check cloud % for
40
+ timeframe = datetime.timedelta(hours=1, minutes=30)
41
+ for hour in data['hourly']:
42
+ # dt property is unix utc time of forecasted data - convert this to python datetime object
43
+ forecast_time = datetime.datetime.fromtimestamp(
44
+ hour['dt'], tz=datetime.timezone.utc)
45
+ if abs(forecast_time - gmt_time) <= timeframe:
46
+ # cloud pct is stored in each hour at top level
47
+ cloud_pct.append(hour['clouds'])
48
+ forecast_times.append(forecast_time)
49
+
50
+ return cloud_pct, forecast_times
51
+
52
+
53
+ def weatherapi_to_result(lat, lon, gmt_time):
54
+ """
55
+ API docs: https://www.weatherapi.com/docs/
56
+ TODO: implement wrapper instead https://github.com/weatherapicom/weatherapi-Python
57
+
58
+ Parameters
59
+ ------------
60
+ lat [float]: decimal valued latitude
61
+ lon [float]: decimal values longitude
62
+ gmt_time [datetime object]: time of desired forecast, in gmt and as python datetime object
63
+
64
+ Returns
65
+ --------
66
+ cloud_pct Tuple(List, List): list of cloud percent and corresponding time for times within 1.5 hours of input GMT time
67
+ """
68
+ request_url = f'http://api.weatherapi.com/v1/forecast.json?key={WEATHERAPI_KEY}&q={lat},{lon}&days=2&alerts=no'
69
+ response = requests.get(request_url)
70
+
71
+ data = response.json()
72
+
73
+ timezone = data['location']['tz_id']
74
+
75
+ cloud_pct = []
76
+ forecast_times = []
77
+
78
+ # quick error handling to make sure input time python object has "timezone" property attached
79
+ try:
80
+ gmt_time = gmt_time.astimezone(datetime.timezone.utc)
81
+ except:
82
+ gmt_time = gmt_time.tz_localize('utc')
83
+
84
+ # timeframe around input time to check cloud % for
85
+ timeframe = datetime.timedelta(hours=1, minutes=30)
86
+
87
+ # this api is first divided into days, then hours
88
+ for day in data['forecast']['forecastday']:
89
+ for hour in day['hour']:
90
+ # time_epoch contains unix epoch time in GMT/UTC
91
+ #forecast_time = datetime.datetime.fromtimestamp(hour['time_epoch'], ZoneInfo(timezone))
92
+ forecast_time = datetime.datetime.fromtimestamp(
93
+ hour['time_epoch'], datetime.timezone.utc)
94
+ if abs(forecast_time - gmt_time) <= timeframe:
95
+ cloud_pct.append(hour['cloud'])
96
+ forecast_times.append(
97
+ forecast_time.astimezone(datetime.timezone.utc))
98
+
99
+ return cloud_pct, forecast_times
100
+
101
+
102
+ def met_to_result(lat, lon, gmt_time):
103
+ """
104
+ API doc: https://api.met.no/weatherapi/locationforecast/2.0/documentation
105
+ How to: https://api.met.no/doc/locationforecast/HowTO
106
+
107
+ Parameters
108
+ ------------
109
+ lat [float]: decimal valued latitude
110
+ lon [float]: decimal values longitude
111
+ gmt_time [datetime object]: time of desired forecast, in gmt and as python datetime object
112
+
113
+ Returns
114
+ --------
115
+ cloud_pct Tuple(List, List): list of cloud percent and corresponding time for times within 1.5 hours of input GMT time
116
+ """
117
+
118
+ # set user agent https://stackoverflow.com/questions/10606133/sending-user-agent-using-requests-library-in-python
119
+ # must be unique per API Terms of Service https://api.met.no/doc/TermsOfService
120
+ headers = {
121
+ 'User-Agent': 'NASAEarthScienceRemoteSensingUnit [email protected]'}
122
+
123
+ request_url = f'https://api.met.no/weatherapi/locationforecast/2.0/compact?lat={lat}&lon={lon}'
124
+
125
+ response = requests.get(request_url, headers=headers)
126
+
127
+ data = response.json()
128
+
129
+ cloud_pct = []
130
+ forecast_times = []
131
+
132
+ # timeframe around input time to check cloud % for
133
+ timeframe = datetime.timedelta(hours=1, minutes=30)
134
+
135
+ # walk through json return
136
+ for hour in data['properties']['timeseries']:
137
+ # time is utc formatted time https://api.met.no/doc/locationforecast/FAQ
138
+ forecast_time = datetime.datetime.strptime(
139
+ hour['time'], '%Y-%m-%dT%H:%M:%SZ').replace(tzinfo=datetime.timezone.utc)
140
+ # check if time of forecast is withing "timeframe" of desired time
141
+ if abs(forecast_time - gmt_time) <= timeframe:
142
+ # grab cloud pct from location within the nested json, add to list
143
+ cloud_pct.append(hour['data']['instant']
144
+ ['details']['cloud_area_fraction'])
145
+ # add time of forecast to list. Should be an "on the hour" time
146
+ forecast_times.append(forecast_time)
147
+
148
+ return cloud_pct, forecast_times
149
+
150
+ ################
151
+ # generate text
152
+ ################
153
+
154
+
155
+ def file_to_cloud_listing(input_file, services):
156
+ """
157
+
158
+ Args:
159
+ input_file (Union[str, gradio FileType]): input csv file with LAT, LON, SITE, GMT cols
160
+ services (List): list of weather api servies to check
161
+
162
+ Returns:
163
+ str: formatted string with weather predictions for locations
164
+ """
165
+ # this works if the input is from gradio. Then the file has an name property
166
+ try:
167
+ sites = pd.read_csv(input_file.name, parse_dates=['GMT'])
168
+ using_gradio = True
169
+ except:
170
+ # this is for input from a script or command line
171
+ sites = pd.read_csv(input_file, parse_dates=['GMT'])
172
+ using_gradio = False
173
+ start = time.perf_counter()
174
+ date_format = "%H:%M"
175
+ text = ''
176
+ # each row is a site. Get weather data and then print it for each service for each site.
177
+ for row_idx, row in sites.iterrows():
178
+ #time_of_interest = datetime.datetime.strptime(row.GMT, '%m/%d/%y %H:%M')
179
+ text += check_row(row, services, date_format)
180
+ text += f'{"="*60}\n'
181
+
182
+ return text
183
+
184
+
185
+ def check_row(row, services, date_format="%H:%M"):
186
+ """Check a row of data (a pd.Series with LAT, LON, GMT, SITE cols)
187
+
188
+ Args:
189
+ row (pd.Series): pd.Series with LAT, LON, GMT, SITE cols)
190
+ services (List): List of weather services (['OpenWeather', 'MET (Norwegian)', 'WeatherAPI'] or subset)
191
+ date_format (str, optional): Format for printing time of site pass over. Defaults to "%H:%M".
192
+
193
+ Returns:
194
+ str: formatted str of text for weather vals
195
+ """
196
+ text = ""
197
+
198
+ text += f'{"Location":13}:\t\t{row.SITE} @ {row["GMT"].strftime(date_format)} GMT\n'
199
+
200
+ if not isinstance(row.GMT, datetime.datetime):
201
+ GMT = row["GMT"].to_pydatetime()
202
+ else:
203
+ GMT = row["GMT"]
204
+ GMT = GMT.replace(tzinfo=datetime.timezone.utc)
205
+ if 'OpenWeather' in services:
206
+ try:
207
+ cldp, times = openweather_to_result(row.LAT, row.LON, GMT)
208
+ text += format_cldp_and_time("OpenWeather", cldp=cldp, times=times)
209
+ except Exception as e:
210
+ text += f'OpenWeather:\t\tError {e} in API processing\n'
211
+ if 'MET (Norwegian)' in services:
212
+ try:
213
+ cldp, times = met_to_result(row.LAT, row.LON, GMT)
214
+ text += format_cldp_and_time("Norwegian", cldp=cldp)
215
+ except Exception as e:
216
+ text += f'Norwegian:\t\tError {e} in API processing\n'
217
+ if 'WeatherAPI' in services:
218
+ try:
219
+ cldp, times = weatherapi_to_result(row.LAT, row.LON, GMT)
220
+ text += format_cldp_and_time("WeatherAPI", cldp=cldp)
221
+ except Exception as e:
222
+ text += f'WeatherAPI:\t\tError {e} in API processing\n'
223
+
224
+ return text
225
+
226
+
227
+ def format_cldp_and_time(api_name, cldp, times=None):
228
+ """Formats output text for lists of cloud percents and forecast times
229
+
230
+ Args:
231
+ api_name ([type]): Name of weather source.
232
+ cldp (List): List of floating point cloud percentage values.
233
+ times (List, optional): List of forecast times, as datetime objects. Defaults to None.
234
+
235
+ Returns:
236
+ str: formatted text for printing
237
+ """
238
+ text = ''
239
+ date_format = "%H:%M"
240
+ if times is not None:
241
+ text += f'{"Forecast Time:":13}\t\t' + ' '.join(time.strftime(date_format)
242
+ for time in times) + "\n"
243
+
244
+ text += f'{api_name:13}:\t\t{" ".join(f"{p:<6.0f}" for p in cldp)}\n'
245
+ return text
246
+
247
+
248
+ if __name__ == '__main__':
249
+ inputs = [gr.inputs.File(label='Site File with Lat/Lon and GMT Time'), gr.inputs.CheckboxGroup(label='Weather Services',
250
+ choices=['OpenWeather', 'MET (Norwegian)', 'WeatherAPI'], default=['OpenWeather', 'MET (Norwegian)'])]
251
+ outputs = gr.outputs.Textbox(
252
+ label='Cloud % for hour before, hour of, hour after')
253
+ css = """* {
254
+ font-family: "Lucida Console", "Courier New", monospace !important;/* <-- fonts */
255
+ }"""
256
+ gr.Interface(fn=file_to_cloud_listing, inputs=inputs, css=css, outputs=outputs,
257
+ allow_screenshot=False).launch(auth=("es", 'rs'), share=True)
258
+
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ pandas
2
+ gradio
3
+ requests