commit 2184a5b077a511f08743a7d39639e49a4b4d6638 Author: Dessa Simpson Date: Wed Jul 10 21:42:11 2024 -0700 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6e4266f --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +__pycache__/ +*.py[cod] +*$py.class diff --git a/README.md b/README.md new file mode 100644 index 0000000..35eb7d9 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# Xaphoon +A different take on phoon, derived in part from pyphoon. diff --git a/moons.py b/moons.py new file mode 100644 index 0000000..4e23ccb --- /dev/null +++ b/moons.py @@ -0,0 +1,285 @@ +""" +Moon backgrounds as ascii + +The MIT License (MIT) + +Copyright (c) 2016 Igor Chubin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +backgrounds = {} + +backgrounds[6] = [ + r" ..--.. ", + r" .` oo .`. ", + r" .o~. O ).", + r" .c`_..() ||.", + r" `..o....'/ ", + r" `'--'' ", +] +backgrounds[15] = [ + r" .---------. ", + r" .--' o . `--. ", + r" .'@ @@@@@@ . . `. ", + r" .' . @@@@@@@@ @@@@@@ `. ", + r" / @@o @@@@@@. @@@@ O @\ ", + r" |@@@ @@@@@@ @@| ", + r" / @@@@ `.-. @@@@@@@@ @@"+"\\", + r" |@ @@ @@@@@@ @@ |", + r" \ @@ @ .() @@ @@@@ /", + r" | @@@ @@@ @@ | ", + r" \ . @@ @\ . . @@ o / ", + r" `. @@@@ _\ / . o .' ", + r" `. / | o .' ", + r" `--./ . .--' ", + r" `---------' " +] + +backgrounds[17] = [ + r" .---------. ", + r" .--' o . `--. ", + r" .'@ @@@@@@ . . `. ", + r" .'@@ @@@@@@@@ @@@@ . `. ", + r" .' . @@@@@@@@ @@@@@@ . `. ", + r" / @@ o @@@@@@. @@@@ O @\ ", + r" |@@@@ @@@@@@ @@| ", + r" / @@@@@ `.-. @@@@@@@@ . @@"+"\\", + r" |@ @@ @@@@@@ @@@ |", + r" \ @@ @ .() @@ @@@@@ /", + r" | @ @@@ @@@ @@@ | ", + r" \ . @@ @\ . . @@ o / ", + r" `. @@@@ _\ / . o .' ", + r" `. @@ ()-- .' ", + r" `. / | o .' ", + r" `--./ . .--' ", + r" `---------' " +] + +backgrounds[18] = [ + r" .----------. ", + r" .--' o . `--. ", + r" .'@ @@@@@@ O . . `. ", + r" .'@@ @@@@@@@@ @@@@ . `. ", + r" .' . @@@@@@@@ @@@@@@ . `. ", + r" / @@ o @@@@@@. @@@@ O @\ ", + r" |@@@@ @@@@@@ @@| ", + r" / @@@@@ `.-. . @@@@@@@@ . @@"+"\\", + r" | @@@@ --`-' . o @@@@@@@ |", + r" |@ @@ @@@@@@ @@@ |", + r" \ @@ @ . () @@ @@@@@ /", + r" | @ @@@ @@@ @@@ | ", + r" \ . @@ @\ . . @@ o / ", + r" `. @@@@ _\ / . o .' ", + r" `. @@ ()--- .' ", + r" `. / | . o .' ", + r" `--./ . .--' ", + r" `----------' " +] + +backgrounds[19] = [ + r" .----------. ", + r" .--' o . `--. ", + r" .-'@ @@@@@@ O . . `-. ", + r" .' @@ @@@@@@@@ @@@@ . `. ", + r" / . @@@@@@@@ @@@@@@ . \ ", + r" /@@ o @@@@@@. @@@@ O @\ ", + r" /@@@@ @@@@@@ @@@\ ", + r" . @@@@@ `.-./ . @@@@@@@@ . @@ .", + r" | @@@@ --`-' . @@@@@@@ |", + r" |@ @@ ` o @@@@@@ @@@@ |", + r" | @@ o @@ @@@@@@ |", + r" ` . @ @@ () @@@ @@@@ '", + r" \ @@ @@@@ . @@ . o / ", + r" \ @@@@ @@\ . o / ", + r" \ . @@ _\ / . .-. / ", + r" `. . ()--- `-' .' ", + r" `-. ./ | . o .-' ", + r" `--./ . .--' ", + r" `----------' " +] + +backgrounds[21] = [ + r" .----------. ", + r" .---' O . . `---. ", + r" .-'@ @@@@@@ . @@@@@ `-. ", + r" .'@@ @@@@@@@@@ @@@@@@@ . `. ", + r" / o @@@@@@@@@ @@@@@@@ . \ ", + r" /@ o @@@@@@@@@. @@@@@@@ O \ ", + r" /@@@ . @@@@@@o @@@@@@@@@@ @@\ ", + r" /@@@@@ . @@@@@@@@@@@@@ o @@@\ ", + r" .@@@@@ O `.-./ . @@@@@@@@@@@@ @@ .", + r" | @@@@ --`-' o @@@@@@@@ @@@@ |", + r" |@ @@@ ` o . @@ . @@@@@@@ |", + r" | @@ @ .-. @@@ @@@@@@@ |", + r" ` . @ @@@ `-' . @@@@ @@@@ o '", + r" \ @@ @@@@@ . @@ . / ", + r" \ @@@@ @\@@ / . O . o . / ", + r" \o @@ \ \ / . . / ", + r" \ . .\.-.___ . . .-. / ", + r" `. `-' `-'.' ", + r" `-. o / | o O . .-' ", + r" `---. . . .---' ", + r" `----------' " +] + +backgrounds[22] = [ + r" .------------. ", + r" .--' o . . `--. ", + r" .-' . O . . `-. ", + r" .'@ @@@@@@@ . @@@@@ `. ", + r" .'@@@ @@@@@@@@@@@ @@@@@@@ . `. ", + r" / o @@@@@@@@@@@ @@@@@@@ . \ ", + r" /@@ o @@@@@@@@@@@. @@@@@@@ O \ ", + r" /@@@@ . @@@@@@@o @@@@@@@@@@ @@@\ ", + r" |@@@@@ . @@@@@@@@@@@@ @@@@| ", + r" /@@@@@ O `.-./ . @@@@@@@@@@@ @@ "+"\\", + r" | @@@@ --`-' o . @@@@@@@ @@@@ |", + r" |@ @@@ @@ @ ` o .-. @@ . @@@@@@ |", + r" \ @@@ `-' . @@@ @@@@@@ /", + r" | . @ @@ @@@@@ . @@@@ @@@ o | ", + r" \ @@@@ @\@@ / . O @@ . . / ", + r" \ o @@ \ \ / . . o / ", + r" \ . .\.-.___ . . . .-. / ", + r" `. `-' `-' .' ", + r" `. o / | o O . .' ", + r" `-. / . . .-' ", + r" `--. . .--' ", + r" `------------' " +] + +backgrounds[23] = [ + r" .------------. ", + r" .--' o . . `--. ", + r" .-' . O . . `-. ", + r" .-'@ @@@@@@@ . @@@@@ `-. ", + r" /@@@ @@@@@@@@@@@ @@@@@@@ . \ ", + r" ./ o @@@@@@@@@@@ @@@@@@@ . \. ", + r" /@@ o @@@@@@@@@@@. @@@@@@@ O \ ", + r" /@@@@ . @@@@@@@o @@@@@@@@@@ @@@ \ ", + r" |@@@@@ . @@@@@@@@@@@@@ o @@@@| ", + r" /@@@@@ O `.-./ . @@@@@@@@@@@@ @@ "+"\\", + r" | @@@@ --`-' o @@@@@@@@ @@@@ |", + r" |@ @@@ ` o . @@ . @@@@@@@ |", + r" | @@ @ .-. @@@ @@@@@@@ |", + r" \ . @ @@@ `-' . @@@@ @@@@ o /", + r" | @@ @@@@@ . @@ . | ", + r" \ @@@@ @\@@ / . O . o . / ", + r" \ o @@ \ \ / . . / ", + r" `\ . .\.-.___ . . .-. /' ", + r" \ `-' `-' / ", + r" `-. o / | o O . .-' ", + r" `-. / . . .-' ", + r" `--. . .--' ", + r" `------------' " +] + +backgrounds[24] = [ + r" .------------. ", + r" .---' o . . `---. ", + r" .-' . O . . `-. ", + r" .'@ @@@@@@@ . @@@@@ `. ", + r" .'@@ @@@@@@@@@@@ @@@@@@@ . `. ", + r" / o @@@@@@@@@@@ @@@@@@@ . \ ", + r" /@ o @@@@@@@@@@@. @@@@@@@ O \ ", + r" /@@@ . @@@@@@@o @@@@@@@@@@ @@@ \ ", + r" /@@@@@ . @@@@@@@@@@@@@ o @@@@ \ ", + r" |@@@@ O `.-./ . @@@@@@@@@@@@ @@ | ", + r" / @@@@ --`-' o @@@@@@@@ @@@@ "+"\\", + r" |@ @@@ @ ` . @@ @@@@@@@ |", + r" | @ o @ @@@@@@@ |", + r" \ @@ .-. @@@ @@@@ o /", + r" | . @ @@@ `-' . @@@@ | ", + r" \ @@ @@@@@ . @@ . / ", + r" \ @@@@ @\@@ / . O . o . / ", + r" \ o @@ \ \ / . . / ", + r" \ . .\.-.___ . . .-. / ", + r" `. `-' `-' .' ", + r" `. o / | o O . .' ", + r" `-. / . . .-' ", + r" `---. . .---' ", + r" `------------' " +] + +backgrounds[29] = [ + r" .--------------. ", + r" .---' o . `---. ", + r" .-' . O . . `-. ", + r" .-' @@@@@@ . `-. ", + r" .'@@ @@@@@@@@@@@ @@@@@@@ . `. ", + r" .'@@@ @@@@@@@@@@@@@@ @@@@@@@@@ `. ", + r" /@@@ o @@@@@@@@@@@@@@ @@@@@@@@@ O \ ", + r" / @@@@@@@@@@@@@@ @ @@@@@@@@@ @@ . \ ", + r" /@ o @@@@@@@@@@@ . @@ @@@@@@@@@@@ @@ \ ", + r" /@@@ . @@@@@@ o @ @@@@@@@@@@@@@ o @@@@ \ ", + r" /@@@@@ @ . @@@@@@@@@@@@@@ @@@@@ \ ", + r" |@@@@@ O `.-./ . . @@@@@@@@@@@@@ @@@ | ", + r" / @@@@@ --`-' o @@@@@@@@@@@ @@@ . "+"\\", + r" |@ @@@@ . @ @ ` @ @@ . @@@@@@ |", + r" | @@ o @@ . @@@@@@ |", + r" | . @ @ @ o @@ o @@@@@@. |", + r" \ @ @ @ .-. @@@@ @@@ /", + r" | @ @ @ `-' . @@@@ . . | ", + r" \ . o @ @@@@ . @@ . . / ", + r" \ @@@ @@@@@@ . o / ", + r" \ @@@@@ @@\@@ / O . / ", + r" \ o @@@ \ \ / __ . . .--. / ", + r" \ . . \.-.--- `--' / ", + r" `. `-' . .' ", + r" `. o / | ` O . .' ", + r" `-. / | o .-' ", + r" `-. . . .-' ", + r" `---. . .---' ", + r" `--------------' " +] + +backgrounds[32] = [ + r" .--------------. ", + r" .----' o . `----. ", + r" .-' . O . . `-. ", + r" .-' @@@@@@ . `-. ", + r" .'@ @@@@@@@@@@@ @@@@@@@@ . `. ", + r" .'@@ @@@@@@@@@@@@@@ @@@@@@@@@@ `. ", + r" .'@@@ o @@@@@@@@@@@@@@ @@@@@@@@@@ o `. ", + r" /@@@ @@@@@@@@@@@@@@ @ @@@@@@@@@@ @@ . \ ", + r" / @@@@@@@@@@@ . @@ @@@@@@@@@@@@ @@ \ ", + r" /@ o . @@@@@@ o @ @@@@@@@@@@@@@@ o @@@@ \ ", + r" /@@@ . @@@@@@@@@@@@@@@ @@@@@ \ ", + r" /@@@@@ @ . @@@@@@@@@@@@@@ @@@ \ ", + r" |@@@@@ o `.-./ . @@@@@@@@@@@@ @@@ . | ", + r" / @@@@@ __`-' o @@ . @@@@@@ "+"\\", + r" |@ @@@@ . @ ` @ @@ . @@@@@@ |", + r" | @@ @ o @@@ o @@@@@@. |", + r" | @ @@@@@ @@@ |", + r" | . . @ @ @ o @@@@@ . . |", + r" \ @ .-. . @@@ . . /", + r" | @ @ @ @ `-' . / ", + r" \ . @ @ . o / ", + r" \ o @@@@ . . / ", + r" \ @@@ @@@@@@ . o / ", + r" \ @@@@@ @@\@@ / o . / ", + r" \ o @@@ \ \ / ___ . . .--. / ", + r" `. . \.-.--- `--' .' ", + r" `. `-' . .' ", + r" `. o / | O . .' ", + r" `-. / | o .-' ", + r" `-. . . .-' ", + r" `----. . .----' ", + r" `--------------' " +] diff --git a/pyphoon.py b/pyphoon.py new file mode 100644 index 0000000..a8e10c5 --- /dev/null +++ b/pyphoon.py @@ -0,0 +1,109 @@ +""" +Adapted from pyphoon - Phase of the Moon (Python version) +Igor Chubin , 05.03.2016, + +Based on the original version of Jef Poskanzer +written in Pascal in 1979 (and later translated to C) + +The MIT License (MIT) + +Copyright (c) 2016 Igor Chubin +Copyright (c) 2024 Dessa Simpson + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" +import math +from moons import backgrounds + +# If you change the aspect ratio, the canned backgrounds won't work. +ASPECTRATIO = 0.5 + +def putmoon(pctphase, lines, atfiller, hemisphere): # pylint: disable=too-many-locals,too-many-branches,too-many-statements,too-many-arguments + """Print the moon""" + + output = "" + def putchar(char): + nonlocal output + output += char + + # Find the length of the atfiller string + atflrlen = len(atfiller) + + # Fix waxes and wanes direction for south hemisphere + if hemisphere == 'south': + pctphase = 1 - pctphase + + angphase = pctphase * 2.0 * math.pi + mcap = -math.cos(angphase) + + # Figure out how big the moon is + yrad = lines / 2.0 + xrad = yrad / ASPECTRATIO + + # Now output the moon, a slice at a time + atflridx = 0 + lin = 0 + while lin < lines: + # Compute the edges of this slice + ycoord = lin + 0.5 - yrad + xright = xrad * math.sqrt(1.0 - (ycoord * ycoord) / (yrad * yrad)) + xleft = -xright + if math.pi > angphase >= 0.0: + xleft = mcap * xleft + else: + xright = mcap * xright + + colleft = int(xrad + 0.5) + int(xleft + 0.5) + colright = int(xrad + 0.5) + int(xright + 0.5) + + # Now output the slice + col = 0 + while col < colleft: + putchar(' ') + col += 1 + while col <= colright: + if hemisphere == 'north': + # north - read moons from upper-left to bottom-right + if lines in backgrounds: + char = backgrounds[lines][lin][col] + else: + char = '@' + else: + # south - read moons from bottom-right to upper-left + # equivalent to rotate 180 degress or turn upside-down + if lines in backgrounds: + char = backgrounds[lines][-1-lin][-col] + else: + char = '@' + + # rotate char upside-down if needed + char = char.translate(str.maketrans("().`_'", + ")(`.^,")) + + if char != '@': + putchar(char) + else: + putchar(atfiller[atflridx]) + atflridx = (atflridx + 1) % atflrlen + col += 1 + + putchar('\n') + lin += 1 + + return output diff --git a/xaphoon.py b/xaphoon.py new file mode 100755 index 0000000..1cb8ae2 --- /dev/null +++ b/xaphoon.py @@ -0,0 +1,157 @@ +#!/usr/bin/env python +from datetime import datetime, timezone +import math +import time +from argparse import ArgumentParser +import ephem +from pyphoon import putmoon + +# Second resolution for culmination/illumination calculations +DAY_INCREMENT=1/86400 + +def to_deg(rad): + """Convert radians to a displayable integer number of degrees.""" + return round(math.degrees(rad)) + +def to_timestr(date, local=True): + """Convert a pyephem date to a time string in the local time zone.""" + if local: + date = ephem.localtime(date) + else: + date = date.datetime() + return date.strftime("%H:%M:%S") + +def find_target_rising(moon, me): + """Return the relevant moonrise to base display and calculations off of.""" + if moon.alt == 0: # i would love a better way to do this + me = me.copy() + me.date = me.previous_rising(moon) + return me.next_rising(moon) + if moon.alt > 0: + return me.previous_rising(moon) + # moon.alt < 0 + return me.next_rising(moon) + +def cmp_culmination(moon, me, t): + """Determine whether the culmination is before, after, or at t. + + Returns 0 if t is the culmination, -1 if t if culmination is before t, or 1 + if culmination is after t. Assumes there is exactly one peak elevation, + which seems to cause error of up to about 7 seconds due to float precision. + """ + me.date = t - DAY_INCREMENT + moon.compute(me) + e1 = moon.alt + me.date = t + moon.compute(me) + e2 = moon.alt + me.date = t + DAY_INCREMENT + moon.compute(me) + e3 = moon.alt + + if e1 > e2: + return -1 + if e3 > e2: + return 1 + return 0 + +def find_culmination(moon, me, rising, setting): + """Finds culmination via binary search. + + Assumes rising and setting are from same pass. + """ + moon = moon.copy() + me = me.copy() + t1 = rising + t3 = setting + while True: + t2 = (t1 + t3) / 2 + match cmp_culmination(moon,me,t2): + case 0: return ephem.date(t2) + case -1: t3 = t2 + case 1: t1 = t2 + +def cmp_illumination(moon, me, t): + """Determine whether the moon is waxing, waning, or either full or new. + + Returns 0 if the moon is either full or new, -1 if moon is waning, or 1 + if moon is waxing. + """ + moon = moon.copy() + me = me.copy() + me.date = t - DAY_INCREMENT + moon.compute(me) + i1 = moon.moon_phase + me.date = t + DAY_INCREMENT + moon.compute(me) + i2 = moon.moon_phase + + if i1 > i2: + return -1 + if i1 < i2: + return 1 + return 0 + +def main(): + parser = ArgumentParser() + parser.add_argument("lat", + help="Observer latitude") + parser.add_argument("long", + help="Observer longitude") + 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() + + now = ephem.date(datetime.fromtimestamp(args.time, timezone.utc)) + print(f"Current time: {to_timestr(now)}") + + me = ephem.Observer() + me.date = now + me.lat = args.lat + me.lon = args.long + me.elevation = args.elevation + + moon = ephem.Moon(me) + + az = to_deg(moon.az) + el = to_deg(moon.alt) + print(f"Az: {az}° El: {el}°") + + rising = find_target_rising(moon, me) + setting = me.next_setting(moon) + + print (f"Rise: {to_timestr(rising)} Set: {to_timestr(setting)}") + + culm = find_culmination(moon, me, rising, setting) + + print(f"Culmination: {to_timestr(culm)}") + + direction = cmp_illumination(moon, me, now) + match direction: + case -1: + direction_indicator = '-' + case 0: + direction_indicator = '' + case 1: + direction_indicator = '+' + + print(f"Phase: {moon.moon_phase:.0%}{direction_indicator}") + + # Convert illumination percentage and waxing/waning status to percent through full cycle + if direction < 0: # waning + full_cycle_phase = 1 - (moon.moon_phase / 2) + else: + full_cycle_phase = moon.moon_phase / 2 + + print(putmoon(full_cycle_phase, 20, '@', 'northern' if me.lat > 0 else 'southern')) + +main()