Initial commit

pyephem
Dessa Simpson 2024-07-10 21:42:11 -07:00
commit 2184a5b077
5 changed files with 556 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
__pycache__/
*.py[cod]
*$py.class

2
README.md Normal file
View File

@ -0,0 +1,2 @@
# Xaphoon
A different take on phoon, derived in part from pyphoon.

285
moons.py Normal file
View File

@ -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" `--------------' "
]

109
pyphoon.py Normal file
View File

@ -0,0 +1,109 @@
"""
Adapted from pyphoon - Phase of the Moon (Python version)
Igor Chubin <igor@chub.in>, 05.03.2016,
Based on the original version of Jef Poskanzer <jef@mail.acme.com>
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

157
xaphoon.py Executable file
View File

@ -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()