Add fetch_moon_data.py script to bin/
This commit is contained in:
parent
4ac41e256a
commit
4b2cef086f
1 changed files with 208 additions and 0 deletions
208
bin/fetch_moon_data.py
Executable file
208
bin/fetch_moon_data.py
Executable file
|
@ -0,0 +1,208 @@
|
|||
#!/usr/bin/env python3
|
||||
# Eryn Wells <eryn@erynwells.me>
|
||||
|
||||
'''
|
||||
Fetch moon data from api.met.no.
|
||||
'''
|
||||
|
||||
import argparse
|
||||
import datetime
|
||||
import json
|
||||
import os.path
|
||||
import requests
|
||||
import sys
|
||||
import pytz
|
||||
|
||||
API_URL = 'https://api.met.no/weatherapi/sunrise/2.0/.json'
|
||||
|
||||
# From https://en.wikipedia.org/wiki/San_Francisco
|
||||
SAN_FRANCISCO_LAT_LON = (37.7775, -122.416389)
|
||||
SAN_FRANCISCO_TIMEZONE = 'America/Los_Angeles'
|
||||
|
||||
def parse_args(argv, *a, **kw):
|
||||
parser = argparse.ArgumentParser(*a, **kw)
|
||||
parser.add_argument('--date',
|
||||
help='''
|
||||
Starting date for number of days to fetch in YYYY-MM-DD format.
|
||||
Defaults to today's date.
|
||||
''')
|
||||
parser.add_argument('--days', type=int, default=1,
|
||||
help='''
|
||||
Number of days of data to fetch. The max is 15 per API rules. See
|
||||
https://api.met.no/weatherapi/sunrise/2.0/documentation#Output_format
|
||||
for details.
|
||||
''')
|
||||
parser.add_argument('-l', '--location',
|
||||
default='{0[0]},{0[1]}'.format(SAN_FRANCISCO_LAT_LON),
|
||||
help='''
|
||||
Geolocation in latitude and longitude coordinates separated by a
|
||||
comma. If this argument is omitted, the geolocation of San
|
||||
Francisco, CA, USA will be used instead.
|
||||
''')
|
||||
parser.add_argument('-n', '--dry-run', action='store_true',
|
||||
help="Do everything but write downloaded data to the file.")
|
||||
parser.add_argument('-t', '--timezone', default=SAN_FRANCISCO_TIMEZONE,
|
||||
help='''
|
||||
Timezone to query for. If this argument is omitted,
|
||||
'America/Los_Angeles' will be used.
|
||||
''')
|
||||
parser.add_argument('file')
|
||||
args = parser.parse_args(argv)
|
||||
return args
|
||||
|
||||
def offset_to_timedelta(offset):
|
||||
hours, minutes = offset.split(':')
|
||||
return datetime.timedelta(hours=hours, minutes=minutes)
|
||||
|
||||
def lat_long_from_location(location):
|
||||
try:
|
||||
lat_lon = tuple(round(float(x), 1) for x in location.split(','))
|
||||
except ValueError:
|
||||
print("Unable to parse '{location}' into a lat/lon pair.")
|
||||
return None, None
|
||||
|
||||
if len(lat_lon) != 2:
|
||||
print("Unable to parse '{location}' into a lat/lon pair.")
|
||||
return None, None
|
||||
|
||||
return lat_lon[0], lat_lon[1]
|
||||
|
||||
def download_moon_data(latitude, longitude, date, utc_offset, number_of_days, dry_run):
|
||||
print(f"Downloading {number_of_days} {'days' if number_of_days != 1 else 'day'} of moon data starting {date} {utc_offset}.", file=sys.stderr)
|
||||
|
||||
moon_data = {
|
||||
'location': {}
|
||||
}
|
||||
|
||||
for _ in range(number_of_days):
|
||||
if not dry_run:
|
||||
print(f"Fetching data for {date}.", file=sys.stderr)
|
||||
response = requests.get(API_URL, params={
|
||||
'lat': latitude,
|
||||
'lon': longitude,
|
||||
'date': date.strftime('%Y-%m-%d'),
|
||||
'offset': utc_offset,
|
||||
'days': 1,
|
||||
})
|
||||
|
||||
try:
|
||||
response_json = response.json()
|
||||
except json.JSONDecodeError as e:
|
||||
print(f'Error decoding JSON response: {e}', file=sys.stderr)
|
||||
print(response.text, file=sys.stderr)
|
||||
raise
|
||||
|
||||
moon_data.setdefault('meta', response_json['meta'])
|
||||
moon_data['location'].setdefault('height', response_json['location']['height'])
|
||||
moon_data['location'].setdefault('latitude', response_json['location']['latitude'])
|
||||
moon_data['location'].setdefault('longitude', response_json['location']['longitude'])
|
||||
moon_data['location'].setdefault('time', [])
|
||||
moon_data['location']['time'].append(response_json['location']['time'][0])
|
||||
else:
|
||||
print(f"Dry run. Fetching data for {date} will be skipped.", file=sys.stderr)
|
||||
|
||||
date = date + datetime.timedelta(days=1)
|
||||
|
||||
return moon_data
|
||||
|
||||
def main(argv):
|
||||
args = parse_args(argv[1:], prog=argv[0])
|
||||
|
||||
timezone = pytz.timezone(args.timezone)
|
||||
|
||||
date = None
|
||||
try:
|
||||
date = datetime.datetime.strptime(args.date, '%Y-%m-%d')
|
||||
except ValueError:
|
||||
date = datetime.datetime.now(tz=timezone).date()
|
||||
print("Date must be in YYYY-MM-DD format.", file=sys.stderr)
|
||||
except TypeError:
|
||||
date = datetime.datetime.now(tz=timezone).date()
|
||||
print("No date was given.", file=sys.stderr)
|
||||
|
||||
print(f"Using date: {date}.", file=sys.stderr)
|
||||
|
||||
# Check args.file for some moon data that has already been downloaded.
|
||||
moon_data = None
|
||||
filename = args.file
|
||||
if os.path.isfile(filename):
|
||||
try:
|
||||
with open(filename, 'r') as f:
|
||||
moon_data = json.load(f)
|
||||
except json.JSONDecodeError:
|
||||
print(f"{filename} contains invalid JSON data.", file=sys.stderr)
|
||||
moon_data = None
|
||||
|
||||
if args.location:
|
||||
lat, lon = lat_long_from_location(args.location)
|
||||
else:
|
||||
print("No location given.", file=sys.stderr)
|
||||
lat, lon = None, None
|
||||
|
||||
utc_offset = datetime.datetime.now(tz=pytz.timezone(args.timezone)).strftime('%z')
|
||||
utc_offset = utc_offset[:3] + ':' + utc_offset[3:]
|
||||
|
||||
if moon_data:
|
||||
should_fetch_new_data = False
|
||||
|
||||
moon_data_location = moon_data['location']
|
||||
try:
|
||||
moon_data_lat, moon_data_lon = (
|
||||
float(moon_data_location['latitude']),
|
||||
float(moon_data_location['longitude'])
|
||||
)
|
||||
except KeyError:
|
||||
print(f'{filename} is missing lat,lon information.')
|
||||
should_fetch_new_data = True
|
||||
|
||||
if not should_fetch_new_data and (moon_data_lat != lat or moon_data_lon != lon):
|
||||
print(f"Data for {lat},{lon} requested, but {filename} has data for {moon_data_lat},{moon_data_lon}.", file=sys.stderr)
|
||||
should_fetch_new_data = True
|
||||
|
||||
if not should_fetch_new_data:
|
||||
for record in moon_data_location['time']:
|
||||
try:
|
||||
record_date = datetime.datetime.strptime(record['date'], '%Y-%m-%d').date()
|
||||
except (ValueError, TypeError):
|
||||
continue
|
||||
|
||||
if record_date == date and record.get('moonphase'):
|
||||
print(f"{filename} has existing moon phase data for {date}.", file=sys.stderr)
|
||||
#break
|
||||
else:
|
||||
should_fetch_new_data = True
|
||||
|
||||
if should_fetch_new_data:
|
||||
print(f"{filename} doesn't contain moon data for the given date.",
|
||||
file=sys.stderr)
|
||||
|
||||
if not lat or not lon:
|
||||
print(f"No location given. Using {moon_data_lat},{moon_data_lon} from {filename}.", file=sys.stderr)
|
||||
lat = moon_data_lat
|
||||
lon = moon_data_lon
|
||||
|
||||
try:
|
||||
moon_data = download_moon_data(lat, lon, date, utc_offset, args.days, args.dry_run)
|
||||
if not args.dry_run:
|
||||
with open(filename, 'w') as f:
|
||||
json.dump(moon_data, f, indent=2)
|
||||
else:
|
||||
print(f"Dry run. Dumping moon data to {filename} will be skipped.")
|
||||
except json.JSONDecodeError:
|
||||
return -1
|
||||
|
||||
if not moon_data:
|
||||
print(f"{filename} doesn't contain any moon data.", file=sys.stderr)
|
||||
try:
|
||||
moon_data = download_moon_data(lat, lon, date, utc_offset, args.days, args.dry_run)
|
||||
with open(filename, 'w') as f:
|
||||
json.dump(moon_data, f, indent=2)
|
||||
except json.JSONDecodeError:
|
||||
return -1
|
||||
|
||||
return 0
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
result = main(sys.argv)
|
||||
sys.exit(0 if not result else result)
|
Loading…
Add table
Add a link
Reference in a new issue