2024-07-11 04:42:11 +00:00
|
|
|
#!/usr/bin/env python
|
2024-07-14 08:23:01 +00:00
|
|
|
"""Xaphoon - Displays the phase of the moon as well as other related information."""
|
|
|
|
|
2024-07-11 04:42:11 +00:00
|
|
|
import time
|
2024-07-14 08:23:01 +00:00
|
|
|
|
2024-07-11 04:42:11 +00:00
|
|
|
from argparse import ArgumentParser
|
2024-07-14 08:23:01 +00:00
|
|
|
from datetime import datetime, timezone
|
|
|
|
from skyfield import almanac
|
|
|
|
from skyfield_data import get_skyfield_data_path
|
|
|
|
import skyfield.api
|
2024-07-11 04:42:11 +00:00
|
|
|
|
2024-07-14 08:23:01 +00:00
|
|
|
from pyphoon import putmoon
|
2024-07-11 04:42:11 +00:00
|
|
|
|
2024-07-14 08:23:01 +00:00
|
|
|
# Initialize certain skyfield parameters globally
|
|
|
|
sf_load = skyfield.api.Loader(get_skyfield_data_path(), expire=False) # loader
|
|
|
|
ts = sf_load.timescale(builtin=False) # timescale
|
|
|
|
eph = sf_load('de421.bsp') # ephemerides
|
|
|
|
earth, sun, moon = eph['Earth'], eph['Sun'], eph['Moon'] # moooon
|
2024-07-11 04:42:11 +00:00
|
|
|
|
2024-07-14 08:23:01 +00:00
|
|
|
def to_timestr(t, date=False, local=True):
|
|
|
|
"""Convert a skyfield time to a time string, optionally in the local time zone."""
|
|
|
|
t = t.utc_datetime()
|
2024-07-11 04:42:11 +00:00
|
|
|
if local:
|
2024-07-14 08:23:01 +00:00
|
|
|
t = t.astimezone()
|
|
|
|
if date:
|
|
|
|
return t.strftime('%Y-%m-%d %H:%M:%S')
|
|
|
|
return t.strftime('%H:%M:%S')
|
2024-07-11 04:42:11 +00:00
|
|
|
|
2024-07-14 21:02:00 +00:00
|
|
|
def fmt(cols, t, az, el, phase, illum, moonrise, transit, moonset, hemi):
|
|
|
|
"""Formats data into string to print"""
|
|
|
|
_date = t.utc_datetime().astimezone().strftime('%Y-%m-%d %H:%M:%S') # 18 chars
|
|
|
|
_azel = f"Az:{az.degrees:.0f}° El:{el.degrees:.0f}°".ljust(16) # 16 chars
|
|
|
|
_phil = f"Ph: {phase.degrees:.0f}° Ill:{illum*100:.0f}%".rjust(16) # 16 chars
|
|
|
|
_r = f"R:{to_timestr(moonrise)}" # 10 chars
|
|
|
|
_t = f"T:{to_timestr(transit)}" # 10 chars
|
|
|
|
_s = f"S:{to_timestr(moonset)}" # 10 chars
|
|
|
|
# TODO: 21 needs to scale
|
|
|
|
_moon = putmoon(phase.degrees/360, 21, hemi) # ! scalable width
|
|
|
|
|
|
|
|
# 2 groups of spacing, filling cols minus total RTS width
|
|
|
|
_rts_spacing = ' '*int((cols-30)/2)
|
|
|
|
|
|
|
|
ret = f"{_date.center(cols)}\n"
|
|
|
|
ret += f"{_azel}{' '*(cols-32)}{_phil}\n"
|
|
|
|
# split moon on newlines, right-pad to center moon in original width, center to center
|
|
|
|
# in new width, then rejoin with newlines and tack an extra newline on the end
|
|
|
|
ret += '\n'.join([line.ljust(44).center(cols) for line in _moon.split('\n')]) + '\n'
|
|
|
|
ret += f"{_r}{_rts_spacing}{_t}{_rts_spacing}{_s}"
|
|
|
|
return ret
|
|
|
|
|
2024-07-11 04:42:11 +00:00
|
|
|
def main():
|
2024-07-14 08:23:01 +00:00
|
|
|
"""Main function
|
|
|
|
|
|
|
|
Parses arguments, calculates values, and displays them.
|
|
|
|
"""
|
2024-07-11 04:42:11 +00:00
|
|
|
parser = ArgumentParser()
|
|
|
|
parser.add_argument("lat",
|
2024-07-14 08:23:01 +00:00
|
|
|
help="Observer latitude",
|
|
|
|
type=float)
|
|
|
|
parser.add_argument("lon",
|
|
|
|
help="Observer longitude",
|
|
|
|
type=float)
|
2024-07-11 04:42:11 +00:00
|
|
|
parser.add_argument("elevation",
|
|
|
|
help="Observer elevation in meters",
|
|
|
|
type=int)
|
2024-07-14 21:02:00 +00:00
|
|
|
parser.add_argument("-l", "--lines",
|
|
|
|
help="Number of lines for the output to use (default 25)",
|
|
|
|
default=25,
|
|
|
|
type=int)
|
2024-07-11 04:42:11 +00:00
|
|
|
parser.add_argument("-c", "--columns",
|
|
|
|
help="Number of columns for the output to use (default 70)",
|
|
|
|
default=70,
|
|
|
|
type=int)
|
|
|
|
parser.add_argument("-t", "--time",
|
|
|
|
help="Unix epoch time to perform calculations for",
|
|
|
|
default=time.time(),
|
|
|
|
type=int)
|
|
|
|
args = parser.parse_args()
|
|
|
|
|
2024-07-14 08:23:01 +00:00
|
|
|
t = ts.from_datetime(datetime.fromtimestamp(args.time, timezone.utc)) # current time
|
2024-07-11 04:42:11 +00:00
|
|
|
|
2024-07-14 08:23:01 +00:00
|
|
|
obs_geo = skyfield.api.wgs84.latlon(args.lat, args.lon,
|
|
|
|
elevation_m=args.elevation) # geographic position vector
|
|
|
|
obs = earth + obs_geo # barycentric position vector
|
2024-07-11 04:42:11 +00:00
|
|
|
|
2024-07-14 08:23:01 +00:00
|
|
|
moon_apparent = obs.at(t).observe(moon).apparent()
|
|
|
|
el, az, _ = moon_apparent.altaz('standard')
|
2024-07-11 04:42:11 +00:00
|
|
|
|
2024-07-14 08:23:01 +00:00
|
|
|
# Find relevant moonrise. el is based on apparent location, so accounts
|
|
|
|
# for atmospheric refraction. y shouldn't be needed unless user is near
|
|
|
|
# one of the poles, so ignored for now. First [0] discards y (second
|
|
|
|
# element of tuple); second [] selects from array of moonrises/moonsets
|
|
|
|
if el.degrees > 0:
|
|
|
|
# Moon is up. Find last moonrise in the past 24 hours.
|
|
|
|
moonrise = almanac.find_risings(obs, moon, t-1, t)[0][-1]
|
|
|
|
else:
|
|
|
|
# Moon is not up. Find first moonrise in the next 24 hours.
|
|
|
|
moonrise = almanac.find_risings(obs, moon, t, t+1)[0][0]
|
2024-07-11 04:42:11 +00:00
|
|
|
|
2024-07-14 08:23:01 +00:00
|
|
|
# Find first moonset in the next 24 hours after moonrise.
|
|
|
|
moonset = almanac.find_settings(obs, moon, moonrise, moonrise+1)[0][0]
|
2024-07-11 04:42:11 +00:00
|
|
|
|
2024-07-14 08:23:01 +00:00
|
|
|
transit = almanac.find_transits(obs, moon, moonrise, moonrise+1)[0]
|
2024-07-11 04:42:11 +00:00
|
|
|
|
2024-07-14 08:23:01 +00:00
|
|
|
phase = almanac.moon_phase(eph, t)
|
|
|
|
illum = moon_apparent.fraction_illuminated(sun)
|
2024-07-14 21:02:00 +00:00
|
|
|
hemi = 'north' if args.lat > 0 else 'south'
|
2024-07-11 04:42:11 +00:00
|
|
|
|
2024-07-14 21:02:00 +00:00
|
|
|
#print(phase.degrees/360*100)
|
|
|
|
#print(putmoon(phase.degrees/360, 21, 'north' if args.lat > 0 else 'south'))
|
|
|
|
print(fmt(args.columns, t, az, el, phase, illum, moonrise, transit, moonset, hemi))
|
|
|
|
#print(putmoon(phase.degrees/360, 21, '@', hemi))
|
2024-07-11 04:42:11 +00:00
|
|
|
|
|
|
|
main()
|