#!/usr/bin/env python """Xaphoon - Displays the phase of the moon as well as other related information.""" import time from argparse import ArgumentParser from datetime import datetime, timezone from skyfield import almanac from skyfield_data import get_skyfield_data_path import skyfield.api from pyphoon import putmoon # 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 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() if local: t = t.astimezone() if date: return t.strftime('%Y-%m-%d %H:%M:%S') return t.strftime('%H:%M:%S') def main(): """Main function Parses arguments, calculates values, and displays them. """ parser = ArgumentParser() parser.add_argument("lat", help="Observer latitude", type=float) parser.add_argument("lon", help="Observer longitude", type=float) parser.add_argument("elevation", help="Observer elevation in meters", type=int) 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() t = ts.from_datetime(datetime.fromtimestamp(args.time, timezone.utc)) # current time print(f"Current time: {to_timestr(t)}") obs_geo = skyfield.api.wgs84.latlon(args.lat, args.lon, elevation_m=args.elevation) # geographic position vector obs = earth + obs_geo # barycentric position vector moon_apparent = obs.at(t).observe(moon).apparent() el, az, _ = moon_apparent.altaz('standard') print(f"Az: {az.degrees:.0f}° El: {el.degrees:.0f}°") # 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] # Find first moonset in the next 24 hours after moonrise. moonset = almanac.find_settings(obs, moon, moonrise, moonrise+1)[0][0] print(f"Rise: {to_timestr(moonrise)} Set: {to_timestr(moonset)}") transit = almanac.find_transits(obs, moon, moonrise, moonrise+1)[0] print(f"Transit: {to_timestr(transit)}") phase = almanac.moon_phase(eph, t) print(f"Phase: {phase.degrees:.0f}°") illum = moon_apparent.fraction_illuminated(sun) print(f"Illumination: {illum*100:.0f}%") print(putmoon(phase.degrees/360, 21, '@', 'north' if args.lat > 0 else 'south')) main()