horoscope: add

This commit is contained in:
2023-02-24 21:51:04 +01:00
parent 0317be2c49
commit 79bffac7f9
5 changed files with 341 additions and 0 deletions

4
horoscope/default.nix Normal file
View File

@@ -0,0 +1,4 @@
{poetry2nix}:
poetry2nix.mkPoetryApplication {
projectDir = ./.;
}

54
horoscope/horoscope.py Normal file
View File

@@ -0,0 +1,54 @@
from datetime import datetime
import click
from flatlib.datetime import Datetime
from flatlib.geopos import GeoPos
from flatlib.chart import Chart
import flatlib.const
sign_symbols = {
flatlib.const.ARIES: "",
flatlib.const.TAURUS: "",
flatlib.const.GEMINI: "",
flatlib.const.CANCER: "",
flatlib.const.LEO: "",
flatlib.const.VIRGO: "",
flatlib.const.LIBRA: "",
flatlib.const.SCORPIO: "",
flatlib.const.SAGITTARIUS: "",
flatlib.const.CAPRICORN: "",
flatlib.const.AQUARIUS: "",
flatlib.const.PISCES: "",
}
planet_symbols = {
flatlib.const.SUN: "",
flatlib.const.MOON: "",
flatlib.const.MERCURY: "",
flatlib.const.VENUS: "",
flatlib.const.MARS: "",
flatlib.const.JUPITER: "",
flatlib.const.SATURN: "",
}
def convert_into_stupid_flatlib_format(dt):
return Datetime(dt.strftime("%Y/%m/%d"), dt.strftime("%H:%M"))
@click.command()
@click.option("--latitude", type=click.FLOAT, required=True)
@click.option("--longitude", type=click.FLOAT, required=True)
@click.option("--date", type=click.DateTime(), default=datetime.now())
def main(latitude: float, longitude: float, date: datetime):
flatlib_datetime = convert_into_stupid_flatlib_format(date)
position = GeoPos(latitude, longitude)
chart = Chart(flatlib_datetime, position)
for planet in planet_symbols.keys():
planet_position = chart.getObject(planet)
print(
planet_symbols[planet],
sign_symbols[planet_position.sign],
"" if planet_position.movement() == flatlib.const.RETROGRADE else "",
end="",
)
print()

81
horoscope/poetry.lock generated Normal file
View File

@@ -0,0 +1,81 @@
[[package]]
name = "click"
version = "8.1.3"
description = "Composable command line interface toolkit"
category = "main"
optional = false
python-versions = ">=3.7"
[package.dependencies]
colorama = {version = "*", markers = "platform_system == \"Windows\""}
[[package]]
name = "colorama"
version = "0.4.5"
description = "Cross-platform colored terminal text."
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[[package]]
name = "flatlib"
version = "0.2.3"
description = "Python library for Traditional Astrology"
category = "main"
optional = false
python-versions = "*"
[package.dependencies]
pyswisseph = "2.08.00-1"
[[package]]
name = "numpy"
version = "1.23.1"
description = "NumPy is the fundamental package for array computing with Python."
category = "main"
optional = false
python-versions = ">=3.8"
[[package]]
name = "pyswisseph"
version = "2.08.00-1"
description = "Python extension to the Swiss Ephemeris"
category = "main"
optional = false
python-versions = "*"
[[package]]
name = "pytz"
version = "2021.3"
description = "World timezone definitions, modern and historical"
category = "main"
optional = false
python-versions = "*"
[[package]]
name = "timezonefinder"
version = "5.2.0"
description = "fast python package for finding the timezone of any point on earth (coordinates) offline"
category = "main"
optional = false
python-versions = ">=3.6"
[package.dependencies]
numpy = ">=1.16"
[package.extras]
numba = ["numba (>=0.48)"]
[metadata]
lock-version = "1.1"
python-versions = "^3.8"
content-hash = "657742383232643f2fa13df5686de0cc79c624f9ae9bdb2f0fc96c7a94b5b8bf"
[metadata.files]
click = []
colorama = []
flatlib = []
numpy = []
pyswisseph = []
pytz = []
timezonefinder = []

21
horoscope/pyproject.toml Normal file
View File

@@ -0,0 +1,21 @@
[tool.poetry]
name = "horoscope"
version = "0.1.0"
description = ""
authors = ["Kierán Meinhardt <kmein@posteo.de>"]
[tool.poetry.dependencies]
python = "^3.8"
flatlib = "^0.2.3"
click = "^8.0.3"
timezonefinder = "^5.2.0"
pytz = "^2021.3"
[tool.poetry.scripts]
horoscope = "horoscope:main"
transits-current = "transits:current"
transits-forecast = "transits:forecast"
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"

181
horoscope/transits.py Normal file
View File

@@ -0,0 +1,181 @@
from flatlib import aspects, const
from flatlib.chart import Chart
from flatlib.datetime import Datetime
import pytz
from flatlib.geopos import GeoPos
import timezonefinder
import operator
import click
import itertools
from datetime import datetime, timedelta
tf = timezonefinder.TimezoneFinder()
planets = [
const.SUN,
const.MOON,
const.MERCURY,
const.VENUS,
const.MARS,
const.JUPITER,
const.SATURN,
const.URANUS,
const.NEPTUNE,
const.PLUTO,
]
planet_symbols = {
const.SUN: "",
const.MOON: "",
const.MERCURY: "",
const.VENUS: "",
const.MARS: "",
const.JUPITER: "",
const.SATURN: "",
const.URANUS: "",
const.NEPTUNE: "",
const.PLUTO: "",
}
aspect_symbols = {
const.NO_ASPECT: " ",
const.CONJUNCTION: "",
const.SEXTILE: "",
const.SQUARE: "",
const.TRINE: "",
const.OPPOSITION: "",
}
def convert_into_stupid_flatlib_format(dt):
return Datetime(
dt.strftime("%Y/%m/%d"),
dt.strftime("%H:%M"),
dt.utcoffset().total_seconds() / 3600,
)
here_latitude = 52.52
here_longitude = 13.4
def get_aspects(chart1, chart2, *, threshold):
for planet1 in chart1.objects:
for planet2 in chart2.objects:
aspect = aspects.getAspect(planet1, planet2, const.MAJOR_ASPECTS)
if aspect.exists() and aspect.orb <= threshold:
yield aspect
def get_chart(position, dt_naive):
timezone = pytz.timezone(tf.timezone_at(lat=position.lat, lng=position.lon))
dt_aware = timezone.localize(dt_naive)
return Chart(convert_into_stupid_flatlib_format(dt_aware), position, IDs=planets)
def show_aspect(aspect):
return " ".join(
[
planet_symbols[aspect.active.id],
aspect_symbols[aspect.type],
planet_symbols[aspect.passive.id],
]
)
@click.command()
@click.option("--natal-latitude", type=click.FLOAT, default=here_latitude)
@click.option("--natal-longitude", type=click.FLOAT, default=here_longitude)
@click.option("--natal-date", type=click.DateTime(), default=datetime.now())
@click.option("--transit-latitude", type=click.FLOAT, default=here_latitude)
@click.option("--transit-longitude", type=click.FLOAT, default=here_longitude)
@click.option("--transit-date", type=click.DateTime(), default=datetime.now())
@click.option("--threshold", type=click.FLOAT, default=5)
def forecast(
natal_latitude: float,
natal_longitude: float,
natal_date: datetime,
transit_latitude: float,
transit_longitude: float,
transit_date: datetime,
threshold: float,
):
transit_position = GeoPos(transit_latitude, transit_longitude)
natal_position = GeoPos(natal_latitude, natal_longitude)
natal_chart = get_chart(natal_position, natal_date)
transit_chart = get_chart(transit_position, transit_date)
offset = 0
previous_aspects = set(
show_aspect(a)
for a in get_aspects(natal_chart, transit_chart, threshold=threshold)
)
while True:
then = transit_date + timedelta(minutes=offset)
current_chart = get_chart(transit_position, then)
current_aspects = set(
show_aspect(a)
for a in get_aspects(natal_chart, current_chart, threshold=threshold)
)
entered = current_aspects - previous_aspects
exited = previous_aspects - current_aspects
if entered or exited:
print(
then.strftime("%Y-%m-%d %H:%M"),
"".join([" | +" + a for a in entered] + [" | -" + a for a in exited]),
sep="",
)
previous_aspects = current_aspects
offset += 1
@click.command()
@click.option("--natal-latitude", type=click.FLOAT, default=here_latitude)
@click.option("--natal-longitude", type=click.FLOAT, default=here_longitude)
@click.option("--natal-date", "-D", type=click.DateTime(), default=datetime.now())
@click.option("--transit-latitude", type=click.FLOAT, default=here_latitude)
@click.option("--transit-longitude", type=click.FLOAT, default=here_longitude)
@click.option("--transit-date", "-d", type=click.DateTime(), default=datetime.now())
@click.option("--threshold", "-t", type=click.FLOAT, default=5)
def current(
natal_latitude: float,
natal_longitude: float,
natal_date: datetime,
transit_latitude: float,
transit_longitude: float,
transit_date: datetime,
threshold: float,
):
transit_position = GeoPos(transit_latitude, transit_longitude)
natal_position = GeoPos(natal_latitude, natal_longitude)
natal_chart = get_chart(natal_position, natal_date)
transit_chart = get_chart(transit_position, transit_date)
relevant_aspects = list(
get_aspects(natal_chart, transit_chart, threshold=threshold)
)
def aspect_switch_date(aspect, *, direction=1, threshold):
offset = 0
while True:
then = transit_date + direction * timedelta(days=offset)
current_chart = get_chart(transit_position, then)
aspects = [
show_aspect(a)
for a in get_aspects(natal_chart, current_chart, threshold=threshold)
]
if aspect not in aspects:
return then.date()
offset += 1
for aspect in sorted(relevant_aspects, key=operator.attrgetter("orb")):
aspect_string = show_aspect(aspect)
print(
aspect_switch_date(
aspect_string, direction=-1, threshold=threshold
).isoformat(),
aspect_switch_date(
aspect_string, direction=1, threshold=threshold
).isoformat(),
aspect_string,
)