windborne 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- windborne/__init__.py +76 -0
- windborne/cli.py +544 -0
- windborne/config.py +42 -0
- windborne/data_api.py +784 -0
- windborne/forecasts_api.py +459 -0
- windborne/utils.py +940 -0
- windborne-1.0.0.dist-info/METADATA +25 -0
- windborne-1.0.0.dist-info/RECORD +11 -0
- windborne-1.0.0.dist-info/WHEEL +5 -0
- windborne-1.0.0.dist-info/entry_points.txt +2 -0
- windborne-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,459 @@
|
|
1
|
+
from .config import (FORECASTS_API_BASE_URL,
|
2
|
+
FORECASTS_GRIDDED_URL,
|
3
|
+
FORECASTS_HISTORICAL_URL,
|
4
|
+
FORECASTS_TCS_URL,
|
5
|
+
TCS_SUPPORTED_FORMATS)
|
6
|
+
|
7
|
+
from .utils import (make_api_request,
|
8
|
+
parse_time,
|
9
|
+
download_and_save_nc,
|
10
|
+
save_csv_json,
|
11
|
+
save_as_geojson,
|
12
|
+
save_as_gpx,
|
13
|
+
save_as_kml,
|
14
|
+
save_as_little_r)
|
15
|
+
|
16
|
+
# Point forecasts
|
17
|
+
def get_point_forecasts(coordinates, min_forecast_time=None, max_forecast_time=None, min_forecast_hour=None, max_forecast_hour=None, initialization_time=None, save_to_file=None):
|
18
|
+
# Sanitize coordinates by removing whitespace
|
19
|
+
coordinates = coordinates.replace(" ", "")
|
20
|
+
|
21
|
+
params = {"coordinates": coordinates}
|
22
|
+
|
23
|
+
if not coordinates:
|
24
|
+
print("To get points forecasts you must provide coordinates.")
|
25
|
+
return
|
26
|
+
if min_forecast_time:
|
27
|
+
params["min_forecast_time"] = parse_time(min_forecast_time)
|
28
|
+
if max_forecast_time:
|
29
|
+
params["max_forecast_time"] = parse_time(max_forecast_time)
|
30
|
+
if min_forecast_hour:
|
31
|
+
params["min_forecast_hour"] = int(min_forecast_hour)
|
32
|
+
if max_forecast_hour:
|
33
|
+
params["max_forecast_hour"] = int(max_forecast_hour)
|
34
|
+
if initialization_time:
|
35
|
+
initialization_time = parse_time(initialization_time,init_time_flag=True)
|
36
|
+
params["initialization_time"] = initialization_time
|
37
|
+
|
38
|
+
print("We are initiating handshake procedure with our S3 server.\n")
|
39
|
+
|
40
|
+
response = make_api_request(f"{FORECASTS_API_BASE_URL}/points", params=params)
|
41
|
+
|
42
|
+
if save_to_file:
|
43
|
+
# Save as .json
|
44
|
+
save_csv_json(save_to_file, response, csv_data_key='forecasts')
|
45
|
+
|
46
|
+
return response
|
47
|
+
|
48
|
+
# Gridded forecasts
|
49
|
+
# We return the whole response, not just the url
|
50
|
+
|
51
|
+
# 500hPa geopotential
|
52
|
+
# 850hPa geopotential
|
53
|
+
# 500hPa wind u
|
54
|
+
# 500hPa wind v
|
55
|
+
# 500hPa temperature
|
56
|
+
# 850hPa temperature
|
57
|
+
# wind_u_10m
|
58
|
+
# wind_v_10m
|
59
|
+
# pressure_msl
|
60
|
+
# temperature_2m
|
61
|
+
|
62
|
+
def get_temperature_2m(time, save_to_file=None):
|
63
|
+
params = {}
|
64
|
+
|
65
|
+
if not time:
|
66
|
+
print("To get the gridded output of global 2m temperature forecast you need to provide the time for which to get the forecast.")
|
67
|
+
return
|
68
|
+
else:
|
69
|
+
time_parsed = parse_time(time)
|
70
|
+
params["time"] = time_parsed
|
71
|
+
|
72
|
+
print("We are initiating handshake procedure with our S3 server.\n")
|
73
|
+
|
74
|
+
response = make_api_request(f"{FORECASTS_GRIDDED_URL}/temperature_2m", params=params, return_type='all')
|
75
|
+
|
76
|
+
if save_to_file:
|
77
|
+
download_and_save_nc(save_to_file, response)
|
78
|
+
|
79
|
+
return response
|
80
|
+
|
81
|
+
# not implemented yet
|
82
|
+
def get_dewpoint_2m(time, save_to_file=None):
|
83
|
+
params = {}
|
84
|
+
|
85
|
+
if not time:
|
86
|
+
print("To get the gridded output of global 2m dewpoint forecast you need to provide the time for which to get the forecast.")
|
87
|
+
return
|
88
|
+
else:
|
89
|
+
time_parsed = parse_time(time)
|
90
|
+
params["time"] = time_parsed
|
91
|
+
|
92
|
+
print("We are initiating handshake procedure with our S3 server.\n")
|
93
|
+
response = make_api_request(f"{FORECASTS_GRIDDED_URL}/dewpoint_2m", params=params, return_type='all')
|
94
|
+
|
95
|
+
if save_to_file:
|
96
|
+
download_and_save_nc(save_to_file, response)
|
97
|
+
|
98
|
+
return response
|
99
|
+
|
100
|
+
def get_wind_u_10m(time, save_to_file=None):
|
101
|
+
params = {}
|
102
|
+
|
103
|
+
if not time:
|
104
|
+
print("To get the gridded output of global 10m u-component of wind forecasts you need to provide the time for which to get the forecast.")
|
105
|
+
return
|
106
|
+
else:
|
107
|
+
time_parsed = parse_time(time)
|
108
|
+
params["time"] = time_parsed
|
109
|
+
|
110
|
+
print("We are initiating handshake procedure with our S3 server.\n")
|
111
|
+
response = make_api_request(f"{FORECASTS_GRIDDED_URL}/wind_u_10m", params=params, return_type='all')
|
112
|
+
|
113
|
+
if save_to_file:
|
114
|
+
download_and_save_nc(save_to_file, response)
|
115
|
+
|
116
|
+
return response
|
117
|
+
|
118
|
+
def get_wind_v_10m(time, save_to_file=None):
|
119
|
+
params = {}
|
120
|
+
|
121
|
+
if not time:
|
122
|
+
print("To get the gridded output of global 10m v-component of wind forecasts you need to provide the time for which to get the forecast.")
|
123
|
+
return
|
124
|
+
else:
|
125
|
+
time_parsed = parse_time(time)
|
126
|
+
params["time"] = time_parsed
|
127
|
+
|
128
|
+
print("We are initiating handshake procedure with our S3 server.\n")
|
129
|
+
response = make_api_request(f"{FORECASTS_GRIDDED_URL}/wind_v_10m", params=params, return_type='all')
|
130
|
+
|
131
|
+
if save_to_file:
|
132
|
+
download_and_save_nc(save_to_file, response)
|
133
|
+
|
134
|
+
return response
|
135
|
+
|
136
|
+
def get_500hpa_wind_u(time, save_to_file=None):
|
137
|
+
params = {}
|
138
|
+
|
139
|
+
if not time:
|
140
|
+
print("To get the gridded output of global 500hPa wind u-component of wind forecasts you need to provide the time for which to get the forecast.")
|
141
|
+
return
|
142
|
+
else:
|
143
|
+
time_parsed = parse_time(time)
|
144
|
+
params["time"] = time_parsed
|
145
|
+
|
146
|
+
print("We are initiating handshake procedure with our S3 server.\n")
|
147
|
+
response = make_api_request(f"{FORECASTS_GRIDDED_URL}/500/wind_u", params=params, return_type='all')
|
148
|
+
|
149
|
+
if save_to_file:
|
150
|
+
download_and_save_nc(save_to_file, response)
|
151
|
+
|
152
|
+
return response
|
153
|
+
|
154
|
+
def get_500hpa_wind_v(time, save_to_file=None):
|
155
|
+
params = {}
|
156
|
+
|
157
|
+
if not time:
|
158
|
+
print("To get the gridded output of global 500hPa wind v-component of wind forecasts you need to provide the time for which to get the forecast.")
|
159
|
+
return
|
160
|
+
else:
|
161
|
+
time_parsed = parse_time(time)
|
162
|
+
params["time"] = time_parsed
|
163
|
+
|
164
|
+
print("We are initiating handshake procedure with our S3 server.\n")
|
165
|
+
response = make_api_request(f"{FORECASTS_GRIDDED_URL}/500/wind_v", params=params, return_type='all')
|
166
|
+
|
167
|
+
if save_to_file:
|
168
|
+
download_and_save_nc(save_to_file, response)
|
169
|
+
|
170
|
+
return response
|
171
|
+
|
172
|
+
def get_500hpa_temperature(time, save_to_file=None):
|
173
|
+
params = {}
|
174
|
+
|
175
|
+
if not time:
|
176
|
+
print("To get the gridded output of global 500hPa temperature forecasts you need to provide the time for which to get the forecast.")
|
177
|
+
return
|
178
|
+
else:
|
179
|
+
time_parsed = parse_time(time)
|
180
|
+
params["time"] = time_parsed
|
181
|
+
|
182
|
+
print("We are initiating handshake procedure with our S3 server.\n")
|
183
|
+
response = make_api_request(f"{FORECASTS_GRIDDED_URL}/500/temperature", params=params, return_type='all')
|
184
|
+
|
185
|
+
if save_to_file:
|
186
|
+
download_and_save_nc(save_to_file, response)
|
187
|
+
|
188
|
+
return response
|
189
|
+
|
190
|
+
def get_850hpa_temperature(time, save_to_file=None):
|
191
|
+
params = {}
|
192
|
+
|
193
|
+
if not time:
|
194
|
+
print("To get the gridded output of global 850hPa temperature forecasts you need to provide the time for which to get the forecast.")
|
195
|
+
return
|
196
|
+
else:
|
197
|
+
time_parsed = parse_time(time)
|
198
|
+
params["time"] = time_parsed
|
199
|
+
|
200
|
+
print("We are initiating handshake procedure with our S3 server.\n")
|
201
|
+
response = make_api_request(f"{FORECASTS_GRIDDED_URL}/850/temperature", params=params, return_type='all')
|
202
|
+
|
203
|
+
if save_to_file:
|
204
|
+
download_and_save_nc(save_to_file, response)
|
205
|
+
|
206
|
+
return response
|
207
|
+
|
208
|
+
def get_pressure_msl(time, save_to_file=None):
|
209
|
+
params = {}
|
210
|
+
|
211
|
+
if not time:
|
212
|
+
print("To get the gridded output of global mean sea level pressure forecasts you need to provide the time for which to get the forecast.")
|
213
|
+
return
|
214
|
+
else:
|
215
|
+
time_parsed = parse_time(time)
|
216
|
+
params["time"] = time_parsed
|
217
|
+
|
218
|
+
print("We are initiating handshake procedure with our S3 server.\n")
|
219
|
+
response = make_api_request(f"{FORECASTS_GRIDDED_URL}/pressure_msl", params=params, return_type='all')
|
220
|
+
|
221
|
+
if save_to_file:
|
222
|
+
download_and_save_nc(save_to_file, response)
|
223
|
+
|
224
|
+
return response
|
225
|
+
|
226
|
+
def get_500hpa_geopotential(time, save_to_file=None):
|
227
|
+
params = {}
|
228
|
+
|
229
|
+
if not time:
|
230
|
+
print("To get the gridded output of global 500hPa geopotential forecasts you need to provide the time for which to get the forecast.")
|
231
|
+
return
|
232
|
+
else:
|
233
|
+
time_parsed = parse_time(time)
|
234
|
+
params["time"] = time_parsed
|
235
|
+
|
236
|
+
print("We are initiating handshake procedure with our S3 server.\n")
|
237
|
+
response = make_api_request(f"{FORECASTS_GRIDDED_URL}/500/geopotential", params=params, return_type='all')
|
238
|
+
|
239
|
+
if save_to_file:
|
240
|
+
download_and_save_nc(save_to_file, response)
|
241
|
+
|
242
|
+
return response
|
243
|
+
|
244
|
+
def get_850hpa_geopotential(time, save_to_file=None):
|
245
|
+
params = {}
|
246
|
+
|
247
|
+
if not time:
|
248
|
+
print("To get the gridded output of global 850hPa geopotential forecasts you need to provide the time for which to get the forecast.")
|
249
|
+
return
|
250
|
+
else:
|
251
|
+
time_parsed = parse_time(time)
|
252
|
+
params["time"] = time_parsed
|
253
|
+
|
254
|
+
print("We are initiating handshake procedure with our S3 server.\n")
|
255
|
+
response = make_api_request(f"{FORECASTS_GRIDDED_URL}/850/geopotential", params=params, return_type='all')
|
256
|
+
|
257
|
+
if save_to_file:
|
258
|
+
download_and_save_nc(save_to_file, response)
|
259
|
+
|
260
|
+
return response
|
261
|
+
|
262
|
+
# Historical forecasts
|
263
|
+
# We return the whole response, not just the url
|
264
|
+
|
265
|
+
def get_historical_temperature_2m(initialization_time, forecast_hour, save_to_file=None):
|
266
|
+
params = {}
|
267
|
+
|
268
|
+
if not initialization_time or not forecast_hour:
|
269
|
+
print("To get the historical output of global temperature forecasts you need to provide:\n"
|
270
|
+
"- the initialization time of the forecast\n"
|
271
|
+
"- how many hours after the run time the forecast is valid at.\n")
|
272
|
+
return
|
273
|
+
else:
|
274
|
+
time_parsed = parse_time(initialization_time, init_time_flag=True)
|
275
|
+
params["initialization_time"] = time_parsed
|
276
|
+
params["forecast_hour"] = forecast_hour
|
277
|
+
|
278
|
+
print("We are initiating handshake procedure with our S3 server.\n")
|
279
|
+
|
280
|
+
response = make_api_request(f"{FORECASTS_HISTORICAL_URL}/temperature_2m", params=params, return_type='all')
|
281
|
+
|
282
|
+
if save_to_file:
|
283
|
+
download_and_save_nc(save_to_file, response)
|
284
|
+
|
285
|
+
return response
|
286
|
+
|
287
|
+
def get_historical_500hpa_geopotential(initialization_time, forecast_hour, save_to_file=None):
|
288
|
+
params = {}
|
289
|
+
|
290
|
+
if not initialization_time or not forecast_hour:
|
291
|
+
print("To get the historical output of global 500hPa geopotential forecasts you need to provide:\n"
|
292
|
+
"- the initialization time of the forecast\n"
|
293
|
+
"- how many hours after the run time the forecast is valid at.\n")
|
294
|
+
return
|
295
|
+
else:
|
296
|
+
time_parsed = parse_time(initialization_time,init_time_flag=True)
|
297
|
+
params["initialization_time"] = time_parsed
|
298
|
+
params["forecast_hour"] = forecast_hour
|
299
|
+
|
300
|
+
print("We are initiating handshake procedure with our S3 server.\n")
|
301
|
+
|
302
|
+
response = make_api_request(f"{FORECASTS_HISTORICAL_URL}/500/geopotential", params=params, return_type='all')
|
303
|
+
|
304
|
+
if save_to_file:
|
305
|
+
download_and_save_nc(save_to_file, response)
|
306
|
+
|
307
|
+
return response
|
308
|
+
|
309
|
+
def get_historical_500hpa_wind_u(initialization_time, forecast_hour, save_to_file=None):
|
310
|
+
params = {}
|
311
|
+
|
312
|
+
if not initialization_time or not forecast_hour:
|
313
|
+
print("To get the historical output of global 500hPa wind u forecasts you need to provide:\n"
|
314
|
+
"- the initialization time of the forecast\n"
|
315
|
+
"- how many hours after the run time the forecast is valid at.\n")
|
316
|
+
return
|
317
|
+
else:
|
318
|
+
time_parsed = parse_time(initialization_time,init_time_flag=True)
|
319
|
+
params["initialization_time"] = time_parsed
|
320
|
+
params["forecast_hour"] = forecast_hour
|
321
|
+
|
322
|
+
print("We are initiating handshake procedure with our S3 server.\n")
|
323
|
+
|
324
|
+
response = make_api_request(f"{FORECASTS_HISTORICAL_URL}/500/wind_u", params=params, return_type='all')
|
325
|
+
|
326
|
+
if save_to_file:
|
327
|
+
download_and_save_nc(save_to_file, response)
|
328
|
+
|
329
|
+
return response
|
330
|
+
|
331
|
+
def get_historical_500hpa_wind_v(initialization_time, forecast_hour, save_to_file=None):
|
332
|
+
params = {}
|
333
|
+
|
334
|
+
if not initialization_time or not forecast_hour:
|
335
|
+
print("To get the historical output of global 500hPa wind v forecasts you need to provide:\n"
|
336
|
+
"- the initialization time of the forecast\n"
|
337
|
+
"- how many hours after the run time the forecast is valid at.\n")
|
338
|
+
return
|
339
|
+
else:
|
340
|
+
time_parsed = parse_time(initialization_time, init_time_flag=True)
|
341
|
+
params["initialization_time"] = time_parsed
|
342
|
+
params["forecast_hour"] = forecast_hour
|
343
|
+
|
344
|
+
print("We are initiating handshake procedure with our S3 server.\n")
|
345
|
+
|
346
|
+
response = make_api_request(f"{FORECASTS_HISTORICAL_URL}/500/wind_v", params=params, return_type='all')
|
347
|
+
|
348
|
+
if save_to_file:
|
349
|
+
download_and_save_nc(save_to_file, response)
|
350
|
+
|
351
|
+
return response
|
352
|
+
|
353
|
+
# Other
|
354
|
+
# TCs
|
355
|
+
def get_tropical_cyclones(initialization_time=None, basin=None, save_to_file=None):
|
356
|
+
"""
|
357
|
+
Get tropical cyclone data from the API.
|
358
|
+
|
359
|
+
Args:
|
360
|
+
initialization_time (str): Date in either ISO 8601 format (YYYY-MM-DDTHH:00:00)
|
361
|
+
or compact format (YYYYMMDDHH)
|
362
|
+
where HH must be 00, 06, 12, or 18
|
363
|
+
save_to_file (str, optional): Path to save the response data
|
364
|
+
Supported formats: .json, .csv, .gpx, .geojson, .kml, .little_r
|
365
|
+
|
366
|
+
Returns:
|
367
|
+
dict: API response data or None if there's an error
|
368
|
+
"""
|
369
|
+
params = {}
|
370
|
+
|
371
|
+
if initialization_time:
|
372
|
+
initialization_time_parsed = parse_time(initialization_time, init_time_flag=True)
|
373
|
+
params["initialization_time"] = initialization_time_parsed
|
374
|
+
else:
|
375
|
+
# Madee this for our displaying message when no active tcs found
|
376
|
+
initialization_time = 'latest'
|
377
|
+
|
378
|
+
if basin:
|
379
|
+
if basin not in ['NA', 'EP', 'WP', 'NI', 'SI', 'AU', 'SP']:
|
380
|
+
print("Basin should be one of the following:")
|
381
|
+
print("NA - North Atlantic")
|
382
|
+
print("EP - Eastern Pacific")
|
383
|
+
print("WP - Western Pacific")
|
384
|
+
print("NI - North Indian")
|
385
|
+
print("SI - South West Indian")
|
386
|
+
print("AU - Australian Region")
|
387
|
+
print("SP - South Pacific")
|
388
|
+
exit(44)
|
389
|
+
params["basin"] = basin
|
390
|
+
|
391
|
+
# Response here is a .json
|
392
|
+
response = make_api_request(FORECASTS_TCS_URL, params=params)
|
393
|
+
|
394
|
+
if save_to_file:
|
395
|
+
if '.' not in save_to_file:
|
396
|
+
print("You have to provide a filetype for your output file.")
|
397
|
+
print_tc_supported_formats()
|
398
|
+
exit (4)
|
399
|
+
elif not save_to_file.lower().endswith(TCS_SUPPORTED_FORMATS):
|
400
|
+
print("Unsupported file format.")
|
401
|
+
print_tc_supported_formats()
|
402
|
+
exit(44)
|
403
|
+
elif response == {}:
|
404
|
+
# This should be prior to any check of specific .filetype format check and post filetype valid check
|
405
|
+
# make_api_request covers 403, 404, 502, HTTP, Connections Errors
|
406
|
+
# If we pass all of these and we get an empty dictionary ==> there are no active TCs
|
407
|
+
print("There are no active tropical cyclones for your request\n")
|
408
|
+
print("We didn't save any file on your machine.")
|
409
|
+
# It's pointless to save an empty file
|
410
|
+
# save_response_to_file() will throw error on saving {}
|
411
|
+
elif response is None:
|
412
|
+
print("-------------------------------------------------------")
|
413
|
+
print("You are too quick!\nThe tropical cyclone data for initialization time are not uploaded yet.")
|
414
|
+
print('You may check again in a few hours again.')
|
415
|
+
elif save_to_file.lower().endswith('.csv'):
|
416
|
+
# Flatten for CSV
|
417
|
+
flattened_data = []
|
418
|
+
for cyclone_id, tracks in response.items():
|
419
|
+
for track in tracks:
|
420
|
+
track_data = {
|
421
|
+
'cyclone_id': cyclone_id,
|
422
|
+
'latitude': track['latitude'],
|
423
|
+
'longitude': track['longitude'],
|
424
|
+
'time': track['time']
|
425
|
+
}
|
426
|
+
flattened_data.append(track_data)
|
427
|
+
save_csv_json(save_to_file, {'prediction': flattened_data}, csv_data_key='prediction')
|
428
|
+
elif save_to_file.lower().endswith('.json'):
|
429
|
+
# Direct save for JSON
|
430
|
+
save_csv_json(save_to_file, response)
|
431
|
+
elif save_to_file.lower().endswith('.geojson'):
|
432
|
+
save_as_geojson(save_to_file, response)
|
433
|
+
elif save_to_file.lower().endswith('.gpx'):
|
434
|
+
save_as_gpx(save_to_file, response)
|
435
|
+
elif save_to_file.lower().endswith('.kml'):
|
436
|
+
save_as_kml(save_to_file, response)
|
437
|
+
elif save_to_file.lower().endswith('.little_r'):
|
438
|
+
save_as_little_r(save_to_file, response)
|
439
|
+
|
440
|
+
return response
|
441
|
+
|
442
|
+
def get_initialization_times():
|
443
|
+
"""
|
444
|
+
Get available initialization times for pointy.
|
445
|
+
Returns:
|
446
|
+
dict: API response data or None if there's an error
|
447
|
+
"""
|
448
|
+
|
449
|
+
# Response here is a .json
|
450
|
+
response = make_api_request(f"{FORECASTS_API_BASE_URL}/initialization_times.json")
|
451
|
+
|
452
|
+
return response
|
453
|
+
|
454
|
+
# Tropical cyclones
|
455
|
+
def print_tc_supported_formats():
|
456
|
+
"""Print supported file formats for saving tcs data."""
|
457
|
+
print("Supported formats:")
|
458
|
+
for fmt in TCS_SUPPORTED_FORMATS:
|
459
|
+
print(f" - {fmt}")
|