windborne 1.2.7__py3-none-any.whl → 1.3.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/data_api.py CHANGED
@@ -4,12 +4,12 @@ from datetime import datetime, timezone, timedelta
4
4
  import csv
5
5
  import json
6
6
 
7
- from .api_request import make_api_request
7
+ from .api_request import make_api_request, API_BASE_URL
8
8
  from .observation_formatting import format_little_r, convert_to_netcdf
9
9
  from .utils import to_unix_timestamp, save_arbitrary_response, print_table
10
10
  from .track_formatting import save_track
11
11
 
12
- DATA_API_BASE_URL = "https://sensor-data.windbornesystems.com/api/v1"
12
+ DATA_API_BASE_URL = f"{API_BASE_URL}/data/v1"
13
13
 
14
14
  # ------------
15
15
  # CORE RESOURCES
@@ -123,7 +123,7 @@ def get_super_observations_page(since=None, min_time=None, max_time=None, includ
123
123
  return response
124
124
 
125
125
 
126
- def save_observations_batch(observations, output_file, output_format, output_dir, start_time=None, end_time=None, bucket_hours=6.0, csv_headers=None, custom_save=None, prevent_overwrites=False):
126
+ def save_observations_batch(observations, output_file, output_format, output_dir, start_time=None, end_time=None, bucket_hours=6.0, csv_headers=None, custom_save=None, prevent_overwrites=False, verbose=True):
127
127
  filtered_observations = observations
128
128
  if start_time is not None:
129
129
  filtered_observations = [obs for obs in observations if float(obs['timestamp']) >= start_time]
@@ -138,12 +138,12 @@ def save_observations_batch(observations, output_file, output_format, output_dir
138
138
  if custom_save is not None:
139
139
  custom_save(sorted_observations, output_file)
140
140
  else:
141
- save_observations_to_file(sorted_observations, output_file, csv_headers=csv_headers, prevent_overwrites=prevent_overwrites)
141
+ save_observations_to_file(sorted_observations, output_file, csv_headers=csv_headers, prevent_overwrites=prevent_overwrites, verbose=verbose)
142
142
  else:
143
- save_observations_batch_in_buckets(sorted_observations, output_format, output_dir, bucket_hours=bucket_hours, csv_headers=csv_headers, custom_save=custom_save, prevent_overwrites=prevent_overwrites)
143
+ save_observations_batch_in_buckets(sorted_observations, output_format, output_dir, bucket_hours=bucket_hours, csv_headers=csv_headers, custom_save=custom_save, prevent_overwrites=prevent_overwrites, verbose=verbose)
144
144
 
145
145
 
146
- def save_observations_to_file(sorted_observations, output_file, csv_headers=None, prevent_overwrites=False):
146
+ def save_observations_to_file(sorted_observations, output_file, csv_headers=None, prevent_overwrites=False, verbose=True):
147
147
  if len(sorted_observations) == 0:
148
148
  print(f"Skipping empty file {output_file}")
149
149
  return
@@ -169,10 +169,11 @@ def save_observations_to_file(sorted_observations, output_file, csv_headers=None
169
169
 
170
170
  output_file = f"{base}.{i}.{ext}"
171
171
 
172
- print(f"Saving {len(sorted_observations)} {'observation' if len(sorted_observations) == 1 else 'observations'} to {output_file}")
173
- if len(sorted_observations) > 10_000:
174
- print("This may take a while...")
175
- print("-----------------------------------------------------\n")
172
+ if verbose:
173
+ print("-----------------------------------------------------\n")
174
+ print(f"Saving {len(sorted_observations)} {'observation' if len(sorted_observations) == 1 else 'observations'} to {output_file}")
175
+ if len(sorted_observations) > 10_000:
176
+ print("This may take a while...")
176
177
 
177
178
  if output_file.endswith('.nc'):
178
179
  first_obs_timestamp = float(sorted_observations[0]['timestamp'])
@@ -193,10 +194,11 @@ def save_observations_to_file(sorted_observations, output_file, csv_headers=None
193
194
  with open(output_file, 'w') as file:
194
195
  file.write('\n'.join(little_r_records))
195
196
 
196
- print(f"Saved {len(sorted_observations)} {'observation' if len(sorted_observations) == 1 else 'observations'} to {output_file}")
197
+ if verbose:
198
+ print(f"Saved {len(sorted_observations)} {'observation' if len(sorted_observations) == 1 else 'observations'} to {output_file}")
197
199
 
198
200
 
199
- def save_observations_batch_in_buckets(sorted_observations, output_format, output_dir, bucket_hours=6.0, csv_headers=None, custom_save=None, prevent_overwrites=False):
201
+ def save_observations_batch_in_buckets(sorted_observations, output_format, output_dir, bucket_hours=6.0, csv_headers=None, custom_save=None, prevent_overwrites=False, verbose=True):
200
202
  if output_dir:
201
203
  os.makedirs(output_dir, exist_ok=True)
202
204
  print(f"Files will be saved to {output_dir}")
@@ -245,13 +247,13 @@ def save_observations_batch_in_buckets(sorted_observations, output_format, outpu
245
247
  if custom_save is not None:
246
248
  custom_save(segment, output_file)
247
249
  else:
248
- save_observations_to_file(segment, output_file, csv_headers=csv_headers, prevent_overwrites=prevent_overwrites)
250
+ save_observations_to_file(segment, output_file, csv_headers=csv_headers, prevent_overwrites=prevent_overwrites, verbose=verbose)
249
251
 
250
252
  start_index = i
251
253
  curtime += timedelta(hours=bucket_hours).seconds
252
254
 
253
255
 
254
- def get_observations_core(api_args, csv_headers, get_page, start_time=None, end_time=None, output_file=None, bucket_hours=6.0, output_format=None, output_dir=None, callback=None, custom_save=None, exit_at_end=True):
256
+ def get_observations_core(api_args, csv_headers, get_page, start_time=None, end_time=None, output_file=None, bucket_hours=6.0, output_format=None, output_dir=None, callback=None, custom_save=None, exit_at_end=True, verbose=True):
255
257
  """
256
258
  Fetches observations or superobservations between a start time and an optional end time and saves to files in specified format.
257
259
  Files are broken up into time buckets, with filenames containing the time at the mid-point of the bucket.
@@ -308,7 +310,8 @@ def get_observations_core(api_args, csv_headers, get_page, start_time=None, end_
308
310
  bucket_hours=bucket_hours,
309
311
  csv_headers=csv_headers,
310
312
  custom_save=custom_save,
311
- prevent_overwrites=clear_batches
313
+ prevent_overwrites=clear_batches,
314
+ verbose=verbose
312
315
  )
313
316
 
314
317
  result = iterate_through_observations(get_page, api_args, callback=callback, batch_callback=save_with_context, exit_at_end=exit_at_end, clear_batches=clear_batches, batch_size=batch_size)
@@ -360,11 +363,12 @@ def iterate_through_observations(get_page, args, callback=None, batch_callback=N
360
363
  if callback:
361
364
  callback(response)
362
365
  else:
363
- since_timestamp = since
364
- if since_timestamp > 4_000_000_000: # in nanoseconds rather than seconds
365
- since_timestamp /= 1_000_000_000
366
- since_dt = datetime.fromtimestamp(since_timestamp, timezone.utc)
367
- print(f"Fetched page with {len(observations)} observation(s) updated {since_dt} or later")
366
+ if since is not None:
367
+ since_timestamp = since
368
+ if since_timestamp > 4_000_000_000: # in nanoseconds rather than seconds
369
+ since_timestamp /= 1_000_000_000
370
+ since_dt = datetime.fromtimestamp(since_timestamp, timezone.utc)
371
+ print(f"Fetched page with {len(observations)} observation(s) updated {since_dt} or later")
368
372
 
369
373
  batched_observations.extend(observations)
370
374
 
@@ -407,7 +411,7 @@ def verify_observations_output_format(output_format):
407
411
 
408
412
  exit(1)
409
413
 
410
- def get_observations(start_time, end_time=None, include_updated_at=None, mission_id=None, min_latitude=None, max_latitude=None, min_longitude=None, max_longitude=None, output_file=None, bucket_hours=6.0, output_format=None, output_dir=None, callback=None, custom_save=None, exit_at_end=True):
414
+ def get_observations(start_time, end_time=None, include_updated_at=None, mission_id=None, min_latitude=None, max_latitude=None, min_longitude=None, max_longitude=None, output_file=None, bucket_hours=6.0, output_format=None, output_dir=None, callback=None, custom_save=None, exit_at_end=True, verbose=True):
411
415
  """
412
416
  Fetches observations between a start time and an optional end time and saves to files in specified format.
413
417
  Files are broken up into time buckets, with filenames containing the time at the mid-point of the bucket.
@@ -435,6 +439,7 @@ def get_observations(start_time, end_time=None, include_updated_at=None, mission
435
439
  This allows custom processing or saving in custom formats.
436
440
  custom_save (callable): Optional function to save observations in a custom format.
437
441
  exit_at_end (bool): Whether to exit after fetching all observations or keep polling.
442
+ verbose (bool): Whether to print saving information.
438
443
  """
439
444
 
440
445
  # Headers for CSV files
@@ -456,7 +461,7 @@ def get_observations(start_time, end_time=None, include_updated_at=None, mission
456
461
  'include_mission_name': True
457
462
  }
458
463
 
459
- return get_observations_core(api_args, csv_headers, get_page=get_observations_page, start_time=start_time, end_time=end_time, output_file=output_file, bucket_hours=bucket_hours, output_format=output_format, output_dir=output_dir, callback=callback, custom_save=custom_save, exit_at_end=exit_at_end)
464
+ return get_observations_core(api_args, csv_headers, get_page=get_observations_page, start_time=start_time, end_time=end_time, output_file=output_file, bucket_hours=bucket_hours, output_format=output_format, output_dir=output_dir, callback=callback, custom_save=custom_save, exit_at_end=exit_at_end, verbose=verbose)
460
465
 
461
466
  def poll_observations(**kwargs):
462
467
  """
@@ -475,7 +480,7 @@ def poll_observations(**kwargs):
475
480
 
476
481
  get_observations(**kwargs, exit_at_end=False)
477
482
 
478
- def get_super_observations(start_time, end_time=None, mission_id=None, include_updated_at=True, output_file=None, bucket_hours=6.0, output_format=None, output_dir=None, callback=None, custom_save=None, exit_at_end=True):
483
+ def get_super_observations(start_time, end_time=None, mission_id=None, include_updated_at=True, output_file=None, bucket_hours=6.0, output_format=None, output_dir=None, callback=None, custom_save=None, exit_at_end=True, verbose=True):
479
484
  """
480
485
  Fetches super observations between a start time and an optional end time and saves to files in specified format.
481
486
  Files are broken up into time buckets, with filenames containing the time at the mid-point of the bucket.
@@ -497,10 +502,12 @@ def get_super_observations(start_time, end_time=None, mission_id=None, include_u
497
502
  This allows custom processing or saving in custom formats.
498
503
  custom_save (callable): Optional function to save observations in a custom format.
499
504
  exit_at_end (bool): Whether to exit after fetching all observations or keep polling.
505
+ verbose (bool): Whether to print saving information.
500
506
  """
501
507
  csv_headers = [
502
508
  "timestamp", "id", "time", "latitude", "longitude", "altitude", "humidity",
503
- "mission_name", "pressure", "specific_humidity", "speed_u", "speed_v", "temperature"
509
+ "mission_name", "pressure", "specific_humidity", "speed_u", "speed_v", "temperature",
510
+ "mission_id", "updated_at"
504
511
  ]
505
512
 
506
513
  api_args = {
@@ -512,7 +519,7 @@ def get_super_observations(start_time, end_time=None, mission_id=None, include_u
512
519
  'include_mission_name': True
513
520
  }
514
521
 
515
- return get_observations_core(api_args, csv_headers, get_page=get_super_observations_page, start_time=start_time, end_time=end_time, output_file=output_file, bucket_hours=bucket_hours, output_format=output_format, output_dir=output_dir, callback=callback, custom_save=custom_save, exit_at_end=exit_at_end)
522
+ return get_observations_core(api_args, csv_headers, get_page=get_super_observations_page, start_time=start_time, end_time=end_time, output_file=output_file, bucket_hours=bucket_hours, output_format=output_format, output_dir=output_dir, callback=callback, custom_save=custom_save, exit_at_end=exit_at_end, verbose=verbose)
516
523
 
517
524
  def poll_super_observations(**kwargs):
518
525
  """
@@ -556,7 +563,7 @@ def get_flying_missions(output_file=None, print_results=False):
556
563
  'page_size': page_size
557
564
  }
558
565
 
559
- url = f"{DATA_API_BASE_URL}/missions.json"
566
+ url = f"{DATA_API_BASE_URL}/flying_missions.json"
560
567
  flying_missions_response = make_api_request(url, params=query_params)
561
568
 
562
569
  flying_missions = flying_missions_response.get("missions", [])
@@ -700,21 +707,28 @@ def get_predicted_path(mission_id=None, output_file=None, print_result=False):
700
707
 
701
708
  mission = get_flying_mission(mission_id)
702
709
 
703
- url = f"{DATA_API_BASE_URL}/missions/{mission.get('id')}/prediction.json"
710
+ url = f"{DATA_API_BASE_URL}/missions/{mission.get('id')}/predicted_path.json"
704
711
  response = make_api_request(url)
705
712
 
706
713
  if response is None:
707
714
  return
708
715
 
716
+ prediction = response.get('prediction') if isinstance(response, dict) else None
717
+
709
718
  if output_file:
710
719
  name = mission.get('name', mission_id)
711
- save_track(output_file, {name: response['prediction']}, time_key='time')
720
+ # Save an empty list if prediction is not available to avoid crashes
721
+ save_track(output_file, {name: (prediction or [])}, time_key='time')
712
722
 
713
723
  if print_result:
714
- print("Predicted flight path\n")
715
- print_table(response['prediction'], keys=['time', 'latitude', 'longitude', 'altitude'], headers=['Time', 'Latitude', 'Longitude', 'Altitude'])
724
+ if not prediction:
725
+ print("Predicted flight path\n")
726
+ print("No predicted path available for this mission.")
727
+ else:
728
+ print("Predicted flight path\n")
729
+ print_table(prediction, keys=['time', 'latitude', 'longitude', 'altitude'], headers=['Time', 'Latitude', 'Longitude', 'Altitude'])
716
730
 
717
- return response.get('prediction')
731
+ return prediction
718
732
 
719
733
 
720
734
  def get_current_location(mission_id=None, output_file=None, print_result=False, verify_flying=True):
@@ -769,7 +783,7 @@ def get_flight_path(mission_id=None, output_file=None, print_result=False):
769
783
  print("A mission id is required to get a flight path")
770
784
  return
771
785
 
772
- url = f"{DATA_API_BASE_URL}/missions/{mission_id}/flight_data.json"
786
+ url = f"{DATA_API_BASE_URL}/missions/{mission_id}/flight_path.json"
773
787
  response = make_api_request(url)
774
788
 
775
789
  if response is None: