windborne 1.0.9__py3-none-any.whl → 1.1.1__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 +6 -15
- windborne/api_request.py +227 -0
- windborne/cli.py +53 -60
- windborne/cyclone_formatting.py +210 -0
- windborne/data_api.py +429 -1051
- windborne/forecasts_api.py +186 -305
- windborne/observation_formatting.py +456 -0
- windborne/utils.py +15 -887
- {windborne-1.0.9.dist-info → windborne-1.1.1.dist-info}/METADATA +1 -2
- windborne-1.1.1.dist-info/RECORD +13 -0
- windborne/config.py +0 -42
- windborne-1.0.9.dist-info/RECORD +0 -11
- {windborne-1.0.9.dist-info → windborne-1.1.1.dist-info}/WHEEL +0 -0
- {windborne-1.0.9.dist-info → windborne-1.1.1.dist-info}/entry_points.txt +0 -0
- {windborne-1.0.9.dist-info → windborne-1.1.1.dist-info}/top_level.txt +0 -0
windborne/forecasts_api.py
CHANGED
@@ -1,28 +1,84 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
1
|
+
import requests
|
2
|
+
|
3
|
+
from .utils import (
|
4
|
+
parse_time,
|
5
|
+
save_arbitrary_response
|
6
|
+
)
|
7
|
+
|
8
|
+
from .api_request import (
|
9
|
+
make_api_request
|
10
|
+
)
|
11
|
+
|
12
|
+
from .cyclone_formatting import (
|
13
|
+
save_track_as_geojson,
|
14
|
+
save_track_as_gpx,
|
15
|
+
save_track_as_kml,
|
16
|
+
save_track_as_little_r
|
17
|
+
)
|
18
|
+
|
19
|
+
FORECASTS_API_BASE_URL = "https://forecasts.windbornesystems.com/api/v1"
|
20
|
+
FORECASTS_GRIDDED_URL = f"{FORECASTS_API_BASE_URL}/gridded"
|
21
|
+
FORECASTS_HISTORICAL_URL = f"{FORECASTS_API_BASE_URL}/gridded/historical"
|
22
|
+
FORECASTS_TCS_URL = f"{FORECASTS_API_BASE_URL}/tropical_cyclones"
|
23
|
+
TCS_SUPPORTED_FORMATS = ('.csv', '.json', '.geojson', '.gpx', '.kml', 'little_r')
|
24
|
+
|
15
25
|
|
16
26
|
# 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,
|
18
|
-
|
19
|
-
|
27
|
+
def get_point_forecasts(coordinates, min_forecast_time=None, max_forecast_time=None, min_forecast_hour=None, max_forecast_hour=None, initialization_time=None, output_file=None):
|
28
|
+
"""
|
29
|
+
Get point forecasts from the API.
|
20
30
|
|
21
|
-
|
31
|
+
Args:
|
32
|
+
coordinates (str, list): Coordinates in the format "latitude,longitude"
|
33
|
+
or a list of tuples, lists, or dictionaries with keys 'latitude' and 'longitude'
|
34
|
+
min_forecast_time (str, optional): Minimum forecast time in ISO 8601 format (YYYY-MM-DDTHH:00:00)
|
35
|
+
max_forecast_time (str, optional): Maximum forecast time in ISO 8601 format (YYYY-MM-DDTHH:00:00)
|
36
|
+
min_forecast_hour (int, optional): Minimum forecast hour
|
37
|
+
max_forecast_hour (int, optional): Maximum forecast hour
|
38
|
+
initialization_time (str, optional): Initialization time in ISO 8601 format (YYYY-MM-DDTHH:00:00)
|
39
|
+
output_file (str, optional): Path to save the response data
|
40
|
+
Supported formats: .json, .csv
|
41
|
+
"""
|
22
42
|
|
23
|
-
|
43
|
+
# coordinates should be formatted as a semi-colon separated list of latitude,longitude tuples, eg 37,-121;40.3,-100
|
44
|
+
formatted_coordinates = coordinates
|
45
|
+
|
46
|
+
if isinstance(coordinates, list):
|
47
|
+
coordinate_items = []
|
48
|
+
for coordinate in coordinates:
|
49
|
+
if isinstance(coordinate, tuple) or isinstance(coordinate, list):
|
50
|
+
if len(coordinate) != 2:
|
51
|
+
print("Coordinates should be tuples or lists with two elements: latitude and longitude.")
|
52
|
+
return
|
53
|
+
|
54
|
+
coordinate_items.append(f"{coordinate[0]},{coordinate[1]}")
|
55
|
+
elif isinstance(coordinate, str):
|
56
|
+
coordinate_items.append(coordinate)
|
57
|
+
elif isinstance(coordinate, dict):
|
58
|
+
if 'latitude' in coordinate and 'longitude' in coordinate:
|
59
|
+
coordinate_items.append(f"{coordinate['latitude']},{coordinate['longitude']}")
|
60
|
+
elif 'lat' in coordinate and 'lon' in coordinate:
|
61
|
+
coordinate_items.append(f"{coordinate['lat']},{coordinate['lon']}")
|
62
|
+
elif 'lat' in coordinate and 'long' in coordinate:
|
63
|
+
coordinate_items.append(f"{coordinate['lat']},{coordinate['long']}")
|
64
|
+
elif 'lat' in coordinate and 'lng' in coordinate:
|
65
|
+
coordinate_items.append(f"{coordinate['lat']},{coordinate['lng']}")
|
66
|
+
else:
|
67
|
+
print("Coordinates should be dictionaries with keys 'latitude' and 'longitude'.")
|
68
|
+
return
|
69
|
+
|
70
|
+
formatted_coordinates = ";".join(coordinate_items)
|
71
|
+
|
72
|
+
formatted_coordinates = formatted_coordinates.replace(" ", "")
|
73
|
+
|
74
|
+
if not formatted_coordinates or formatted_coordinates == "":
|
24
75
|
print("To get points forecasts you must provide coordinates.")
|
25
76
|
return
|
77
|
+
|
78
|
+
params = {
|
79
|
+
"coordinates": formatted_coordinates
|
80
|
+
}
|
81
|
+
|
26
82
|
if min_forecast_time:
|
27
83
|
params["min_forecast_time"] = parse_time(min_forecast_time)
|
28
84
|
if max_forecast_time:
|
@@ -35,234 +91,83 @@ def get_point_forecasts(coordinates, min_forecast_time=None, max_forecast_time=N
|
|
35
91
|
initialization_time = parse_time(initialization_time,init_time_flag=True)
|
36
92
|
params["initialization_time"] = initialization_time
|
37
93
|
|
38
|
-
print("
|
94
|
+
print("Generating point forecast...")
|
39
95
|
|
40
96
|
response = make_api_request(f"{FORECASTS_API_BASE_URL}/points", params=params)
|
41
97
|
|
42
|
-
if
|
43
|
-
|
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)
|
98
|
+
if output_file:
|
99
|
+
save_arbitrary_response(output_file, response, csv_data_key='forecasts')
|
133
100
|
|
134
101
|
return response
|
135
102
|
|
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
103
|
|
185
|
-
|
186
|
-
|
104
|
+
def get_gridded_forecast(time, variable, output_file=None):
|
105
|
+
"""
|
106
|
+
Get gridded forecast data from the API.
|
107
|
+
Note that this is primarily meant to be used internally by the other functions in this module.
|
187
108
|
|
188
|
-
|
109
|
+
Args:
|
110
|
+
time (str): Date in either ISO 8601 format (YYYY-MM-DDTHH:00:00)
|
111
|
+
or compact format (YYYYMMDDHH)
|
112
|
+
where HH must be 00, 06, 12, or 18
|
113
|
+
variable (str): The variable you want the forecast for
|
114
|
+
output_file (str, optional): Path to save the response data
|
115
|
+
Supported formats: .nc
|
116
|
+
"""
|
189
117
|
|
190
|
-
def get_850hpa_temperature(time, save_to_file=None):
|
191
118
|
params = {}
|
192
119
|
|
193
120
|
if not time:
|
194
|
-
print("
|
121
|
+
print("Error: the time you want the forecast for is required.")
|
195
122
|
return
|
196
123
|
else:
|
197
124
|
time_parsed = parse_time(time)
|
198
125
|
params["time"] = time_parsed
|
199
126
|
|
200
|
-
|
201
|
-
response = make_api_request(f"{FORECASTS_GRIDDED_URL}/850/temperature", params=params, return_type='all')
|
127
|
+
response = make_api_request(f"{FORECASTS_GRIDDED_URL}/{variable}", params=params, as_json=False)
|
202
128
|
|
203
|
-
if
|
204
|
-
|
129
|
+
if output_file:
|
130
|
+
print(f"Output URL found; downloading to {output_file}...")
|
131
|
+
download_and_save_output(output_file, response)
|
205
132
|
|
206
133
|
return response
|
207
134
|
|
208
|
-
def
|
209
|
-
|
135
|
+
def get_temperature_2m(time, output_file=None):
|
136
|
+
return get_gridded_forecast(time, "temperature_2m", output_file)
|
210
137
|
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
else:
|
215
|
-
time_parsed = parse_time(time)
|
216
|
-
params["time"] = time_parsed
|
138
|
+
# Not yet implemented
|
139
|
+
# def get_dewpoint_2m(time, output_file=None):
|
140
|
+
# return get_gridded_forecast(time, "dewpoint_2m", output_file)
|
217
141
|
|
218
|
-
|
219
|
-
|
142
|
+
def get_wind_u_10m(time, output_file=None):
|
143
|
+
return get_gridded_forecast(time, "wind_u_10m", output_file)
|
220
144
|
|
221
|
-
|
222
|
-
|
145
|
+
def get_wind_v_10m(time, output_file=None):
|
146
|
+
return get_gridded_forecast(time, "wind_v_10m", output_file)
|
223
147
|
|
224
|
-
|
148
|
+
def get_500hpa_wind_u(time, output_file=None):
|
149
|
+
return get_gridded_forecast(time, "500/wind_u", output_file)
|
225
150
|
|
226
|
-
def
|
227
|
-
|
151
|
+
def get_500hpa_wind_v(time, output_file=None):
|
152
|
+
return get_gridded_forecast(time, "500/wind_v", output_file)
|
228
153
|
|
229
|
-
|
230
|
-
|
231
|
-
return
|
232
|
-
else:
|
233
|
-
time_parsed = parse_time(time)
|
234
|
-
params["time"] = time_parsed
|
154
|
+
def get_500hpa_temperature(time, output_file=None):
|
155
|
+
return get_gridded_forecast(time, "500/temperature", output_file)
|
235
156
|
|
236
|
-
|
237
|
-
|
157
|
+
def get_850hpa_temperature(time, output_file=None):
|
158
|
+
return get_gridded_forecast(time, "850/temperature", output_file)
|
238
159
|
|
239
|
-
|
240
|
-
|
160
|
+
def get_pressure_msl(time, output_file=None):
|
161
|
+
return get_gridded_forecast(time, "pressure_msl", output_file)
|
241
162
|
|
242
|
-
|
163
|
+
def get_500hpa_geopotential(time, output_file=None):
|
164
|
+
return get_gridded_forecast(time, "500/geopotential", output_file)
|
243
165
|
|
244
|
-
def get_850hpa_geopotential(time,
|
245
|
-
|
166
|
+
def get_850hpa_geopotential(time, output_file=None):
|
167
|
+
return get_gridded_forecast(time, "850/geopotential", output_file)
|
246
168
|
|
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
169
|
|
262
|
-
|
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):
|
170
|
+
def get_historical_output(initialization_time, forecast_hour, variable, output_file=None):
|
266
171
|
params = {}
|
267
172
|
|
268
173
|
if not initialization_time or not forecast_hour:
|
@@ -271,88 +176,32 @@ def get_historical_temperature_2m(initialization_time, forecast_hour, save_to_fi
|
|
271
176
|
"- how many hours after the run time the forecast is valid at.\n")
|
272
177
|
return
|
273
178
|
else:
|
274
|
-
|
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
|
179
|
+
params["initialization_time"] = parse_time(initialization_time, init_time_flag=True)
|
298
180
|
params["forecast_hour"] = forecast_hour
|
299
181
|
|
300
|
-
|
301
|
-
|
302
|
-
response = make_api_request(f"{FORECASTS_HISTORICAL_URL}/500/geopotential", params=params, return_type='all')
|
182
|
+
response = make_api_request(f"{FORECASTS_HISTORICAL_URL}/{variable}", params=params, as_json=False)
|
303
183
|
|
304
|
-
if
|
305
|
-
|
184
|
+
if output_file:
|
185
|
+
print(f"Output URL found; downloading to {output_file}...")
|
186
|
+
download_and_save_output(output_file, response)
|
306
187
|
|
307
188
|
return response
|
308
189
|
|
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
190
|
|
322
|
-
|
191
|
+
def get_historical_temperature_2m(initialization_time, forecast_hour, output_file=None):
|
192
|
+
return get_historical_output(initialization_time, forecast_hour, "temperature_2m", output_file)
|
323
193
|
|
324
|
-
|
194
|
+
def get_historical_500hpa_geopotential(initialization_time, forecast_hour, output_file=None):
|
195
|
+
return get_historical_output(initialization_time, forecast_hour, "500/geopotential", output_file)
|
325
196
|
|
326
|
-
|
327
|
-
|
197
|
+
def get_historical_500hpa_wind_u(initialization_time, forecast_hour, output_file=None):
|
198
|
+
return get_historical_output(initialization_time, forecast_hour, "500/wind_u", output_file)
|
328
199
|
|
329
|
-
|
200
|
+
def get_historical_500hpa_wind_v(initialization_time, forecast_hour, output_file=None):
|
201
|
+
return get_historical_output(initialization_time, forecast_hour, "500/wind_v", output_file)
|
330
202
|
|
331
|
-
def get_historical_500hpa_wind_v(initialization_time, forecast_hour, save_to_file=None):
|
332
|
-
params = {}
|
333
203
|
|
334
|
-
|
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):
|
204
|
+
def get_tropical_cyclones(initialization_time=None, basin=None, output_file=None):
|
356
205
|
"""
|
357
206
|
Get tropical cyclone data from the API.
|
358
207
|
|
@@ -360,7 +209,7 @@ def get_tropical_cyclones(initialization_time=None, basin=None, save_to_file=Non
|
|
360
209
|
initialization_time (str): Date in either ISO 8601 format (YYYY-MM-DDTHH:00:00)
|
361
210
|
or compact format (YYYYMMDDHH)
|
362
211
|
where HH must be 00, 06, 12, or 18
|
363
|
-
|
212
|
+
output_file (str, optional): Path to save the response data
|
364
213
|
Supported formats: .json, .csv, .gpx, .geojson, .kml, .little_r
|
365
214
|
|
366
215
|
Returns:
|
@@ -391,12 +240,12 @@ def get_tropical_cyclones(initialization_time=None, basin=None, save_to_file=Non
|
|
391
240
|
# Response here is a .json
|
392
241
|
response = make_api_request(FORECASTS_TCS_URL, params=params)
|
393
242
|
|
394
|
-
if
|
395
|
-
if '.' not in
|
243
|
+
if output_file:
|
244
|
+
if '.' not in output_file:
|
396
245
|
print("You have to provide a filetype for your output file.")
|
397
246
|
print_tc_supported_formats()
|
398
247
|
exit (4)
|
399
|
-
elif not
|
248
|
+
elif not output_file.lower().endswith(TCS_SUPPORTED_FORMATS):
|
400
249
|
print("Unsupported file format.")
|
401
250
|
print_tc_supported_formats()
|
402
251
|
exit(44)
|
@@ -412,7 +261,7 @@ def get_tropical_cyclones(initialization_time=None, basin=None, save_to_file=Non
|
|
412
261
|
print("-------------------------------------------------------")
|
413
262
|
print("You are too quick!\nThe tropical cyclone data for initialization time are not uploaded yet.")
|
414
263
|
print('You may check again in a few hours again.')
|
415
|
-
elif
|
264
|
+
elif output_file.lower().endswith('.csv'):
|
416
265
|
# Flatten for CSV
|
417
266
|
flattened_data = []
|
418
267
|
for cyclone_id, tracks in response.items():
|
@@ -424,36 +273,68 @@ def get_tropical_cyclones(initialization_time=None, basin=None, save_to_file=Non
|
|
424
273
|
'time': track['time']
|
425
274
|
}
|
426
275
|
flattened_data.append(track_data)
|
427
|
-
|
428
|
-
elif
|
276
|
+
save_arbitrary_response(output_file, {'prediction': flattened_data}, csv_data_key='prediction')
|
277
|
+
elif output_file.lower().endswith('.json'):
|
429
278
|
# Direct save for JSON
|
430
|
-
|
431
|
-
elif
|
432
|
-
|
433
|
-
elif
|
434
|
-
|
435
|
-
elif
|
436
|
-
|
437
|
-
elif
|
438
|
-
|
279
|
+
save_arbitrary_response(output_file, response)
|
280
|
+
elif output_file.lower().endswith('.geojson'):
|
281
|
+
save_track_as_geojson(output_file, response)
|
282
|
+
elif output_file.lower().endswith('.gpx'):
|
283
|
+
save_track_as_gpx(output_file, response)
|
284
|
+
elif output_file.lower().endswith('.kml'):
|
285
|
+
save_track_as_kml(output_file, response)
|
286
|
+
elif output_file.lower().endswith('.little_r'):
|
287
|
+
save_track_as_little_r(output_file, response)
|
439
288
|
|
440
289
|
return response
|
441
290
|
|
291
|
+
|
442
292
|
def get_initialization_times():
|
443
293
|
"""
|
444
|
-
Get available initialization times
|
445
|
-
|
446
|
-
|
294
|
+
Get available WeatherMesh initialization times (also known as cycle times).
|
295
|
+
|
296
|
+
Returns dict with keys "latest" and "available" (a list)
|
447
297
|
"""
|
448
298
|
|
449
|
-
# Response here is a .json
|
450
299
|
response = make_api_request(f"{FORECASTS_API_BASE_URL}/initialization_times.json")
|
451
300
|
|
452
301
|
return response
|
453
302
|
|
303
|
+
|
454
304
|
# Tropical cyclones
|
455
305
|
def print_tc_supported_formats():
|
456
306
|
"""Print supported file formats for saving tcs data."""
|
457
307
|
print("Supported formats:")
|
458
308
|
for fmt in TCS_SUPPORTED_FORMATS:
|
459
|
-
print(f" - {fmt}")
|
309
|
+
print(f" - {fmt}")
|
310
|
+
|
311
|
+
|
312
|
+
def download_and_save_output(output_file, response):
|
313
|
+
"""
|
314
|
+
Downloads a forecast output from a presigned S3 url contained in a response and saves it as a .nc file.
|
315
|
+
|
316
|
+
Args:
|
317
|
+
output_file (str): Path where to save the .nc file
|
318
|
+
response (str): Response that contains the S3 url to download the data from
|
319
|
+
|
320
|
+
Returns:
|
321
|
+
bool: True if successful, False otherwise
|
322
|
+
"""
|
323
|
+
|
324
|
+
# Add .nc extension if not present
|
325
|
+
if not output_file.endswith('.nc'):
|
326
|
+
output_file = output_file + '.nc'
|
327
|
+
|
328
|
+
try:
|
329
|
+
# Save the content directly to file
|
330
|
+
with open(output_file, 'wb') as f:
|
331
|
+
f.write(response.content)
|
332
|
+
print(f"Data Successfully saved to {output_file}")
|
333
|
+
return True
|
334
|
+
|
335
|
+
except requests.exceptions.RequestException as e:
|
336
|
+
print(f"Error downloading the file: {e}")
|
337
|
+
return False
|
338
|
+
except Exception as e:
|
339
|
+
print(f"Error processing the file: {e}")
|
340
|
+
return False
|