Spaces:
Sleeping
Sleeping
import gradio as gr | |
import matplotlib.pyplot as plt | |
from datetime import datetime | |
from dateutil import parser | |
from io import BytesIO | |
from PIL import Image | |
from geopy.geocoders import Nominatim | |
from timezonefinder import TimezoneFinder | |
import pytz | |
import swisseph as swe | |
# Initialize Swiss Ephemeris | |
swe.set_ephe_path(None) | |
# Russian translations for planets | |
PLANET_RU = { | |
'Sun': 'Солнце', 'Moon': 'Луна', 'Mercury': 'Меркурий', | |
'Venus': 'Венера', 'Mars': 'Марс', | |
'Jupiter': 'Юпитер', 'Saturn': 'Сатурн' | |
} | |
# Planet symbols for plotting | |
PLANET_SYMBOLS = { | |
'Sun': '☉', 'Moon': '☾', 'Mercury': '☿', 'Venus': '♀', | |
'Mars': '♂', 'Jupiter': '♃', 'Saturn': '♄' | |
} | |
# Zodiac signs in Russian | |
ZODIAC_SIGNS = [ | |
"Овен", "Телец", "Близнецы", "Рак", "Лев", "Дева", | |
"Весы", "Скорпион", "Стрелец", "Козерог", "Водолей", "Рыбы" | |
] | |
def parse_query(query): | |
"""Parse the query into date, time, and location.""" | |
if not query.startswith("PLadder "): | |
return None, None, "Query must start with 'PLadder'" | |
try: | |
parts = query.split(maxsplit=3) | |
if len(parts) < 4: | |
return None, None, "Incomplete query (need date, time, and location)" | |
_, date_str, time_str, location = parts | |
dt = parser.parse(f"{date_str} {time_str}") | |
return dt, location, None | |
except ValueError as e: | |
return None, None, f"Invalid format: {str(e)}" | |
def get_utc_time(dt, location): | |
"""Convert local time to UTC using location's time zone.""" | |
geolocator = Nominatim(user_agent="pladder_app") | |
try: | |
loc = geolocator.geocode(location, timeout=10) | |
if not loc: | |
return None, None, None, "Location not found" | |
lat, lon = loc.latitude, loc.longitude | |
tz_str = TimezoneFinder().timezone_at(lng=lon, lat=lat) | |
if not tz_str: | |
return None, None, None, "Time zone not found" | |
tz = pytz.timezone(tz_str) | |
local_dt = tz.localize(dt) | |
utc_dt = local_dt.astimezone(pytz.UTC) | |
return utc_dt, lat, lon, None | |
except Exception as e: | |
return None, None, None, f"Error: {str(e)}" | |
def format_coords(lat, lon): | |
"""Format coordinates as degrees, minutes, seconds.""" | |
def dms(value, pos_dir, neg_dir): | |
direction = pos_dir if value >= 0 else neg_dir | |
abs_value = abs(value) | |
degrees = int(abs_value) | |
minutes = int((abs_value - degrees) * 60) | |
seconds = int(round(((abs_value - degrees) * 60 - minutes) * 60)) | |
# Handle rounding overflow | |
if seconds >= 60: | |
seconds -= 60 | |
minutes += 1 | |
if minutes >= 60: | |
minutes -= 60 | |
degrees += 1 | |
return f"{degrees}°{minutes:02}'{seconds:02}\" {direction}" | |
return f"{dms(lat, 'N', 'S')}, {dms(lon, 'E', 'W')}" | |
def lon_to_sign(lon_deg): | |
""" | |
Convert ecliptic longitude to zodiac sign with position. | |
Now includes seconds in the output. | |
""" | |
sign_idx = int(lon_deg // 30) | |
degrees_in_sign = lon_deg % 30 | |
degrees = int(degrees_in_sign) | |
minutes = int((degrees_in_sign - degrees) * 60) | |
seconds = int(round(((degrees_in_sign - degrees) * 60 - minutes) * 60)) | |
# Handle rounding overflow | |
if seconds >= 60: | |
seconds -= 60 | |
minutes += 1 | |
if minutes >= 60: | |
minutes -= 60 | |
degrees += 1 | |
return f"{ZODIAC_SIGNS[sign_idx]} {degrees}°{minutes:02}'{seconds:02}\"" | |
def PLadder_ZSizes(utc_dt, lat, lon): | |
"""Calculate Planetary Ladder and Zone Sizes using Swiss Ephemeris.""" | |
if not -13000 <= utc_dt.year <= 17000: | |
return {"error": "Date out of supported range (-13,000–17,000 CE)"} | |
# Planet mapping with Swiss Ephemeris constants | |
PLANET_OBJECTS = { | |
'Sun': swe.SUN, 'Moon': swe.MOON, 'Mercury': swe.MERCURY, | |
'Venus': swe | |
.VENUS, 'Mars': swe.MARS, | |
'Jupiter': swe.JUPITER, 'Saturn': swe.SATURN | |
} | |
# Calculate Julian Day | |
jd_utc = swe.julday( | |
utc_dt.year, utc_dt.month, utc_dt.day, | |
utc_dt.hour + utc_dt.minute/60 + utc_dt.second/3600 | |
) | |
# Calculate planetary positions | |
longitudes = {} | |
for planet, planet_id in PLANET_OBJECTS.items(): | |
flags = swe.FLG_SWIEPH | swe.FLG_SPEED | |
xx, _ = swe.calc_ut(jd_utc, planet_id, flags) | |
lon = xx[0] % 360 # Normalize to 0-360° | |
longitudes[planet] = lon | |
# Sort planets by longitude | |
sorted_planets = sorted(longitudes.items(), key=lambda x: x[1]) | |
PLadder = [p for p, _ in sorted_planets] | |
sorted_lons = [lon for _, lon in sorted_planets] | |
# Calculate zone sizes | |
zone_sizes = [sorted_lons[0]] # First zone | |
zone_sizes.extend(sorted_lons[i+1] - sorted_lons[i] for i in range(6)) # Middle zones | |
zone_sizes.append(360 - sorted_lons[-1]) # Last zone | |
# Classify zone sizes | |
ZSizes = [] | |
for i, size in enumerate(zone_sizes): | |
# Get bordering planets | |
if i == 0: | |
bord = [PLadder[0]] | |
elif i == len(zone_sizes)-1: | |
bord = [PLadder[-1]] | |
else: | |
bord = [PLadder[i-1], PLadder[i]] | |
# Determine zone classification | |
if any(p in ['Sun', 'Moon'] for p in bord): | |
X = 7 | |
elif any(p in ['Mercury', 'Venus', 'Mars'] for p in bord): | |
X = 6 | |
else: | |
X = 5 | |
# Format size with seconds | |
d = int(size) | |
m = int((size - d) * 60) | |
s = int(round(((size - d) * 60 - m) * 60)) | |
# Handle rounding overflow | |
if s >= 60: | |
s -= 60 | |
m += 1 | |
if m >= 60: | |
m -= 60 | |
d += 1 | |
classification = ( | |
'Swallowed' if size <= 1 else | |
'Tiny' if size <= X else | |
'Small' if size <= 40 else | |
'Ideal' if 50 <= size <= 52 else | |
'Normal' if size < 60 else | |
'Big' | |
) | |
ZSizes.append((f"{d}°{m:02}'{s:02}\"", classification)) | |
return { | |
'PLadder': PLadder, | |
'ZSizes': ZSizes, | |
'longitudes': longitudes # Raw degrees for calculations | |
} | |
def plot_pladder(PLadder): | |
"""Generate the original version of the planetary ladder visualization.""" | |
fig, ax = plt.subplots(figsize=(6, 6)) | |
# Draw the main triangle | |
ax.plot([0, 1.5, 3, 0], [0, 3, 0, 0], 'k-', linewidth=2) | |
# Draw horizontal divisions (original style) | |
ax.plot([0.5, 2.5], [1, 1], 'k--') | |
ax.plot([1, 2], [2, 2], 'k--') | |
# Original planet symbol positions | |
symbol_positions = [ | |
(-0.2, 0.2), (0.3, 1.2), (0.8, 2.2), | |
(1.5, 3.2), (2.2, 2.2), (2.7, 1.2), (3.2, 0.2) | |
] | |
# Add planet symbols | |
for (x, y), planet in zip(symbol_positions, PLadder): | |
ax.text(x, y, PLANET_SYMBOLS[planet], | |
ha='center', va='center', | |
fontsize=24) | |
# Configure plot appearance (original style) | |
ax.set_xlim(-0.5, 3.5) | |
ax.set_ylim(-0.5, 3.5) | |
ax.set_aspect('equal') | |
ax.axis('off') | |
return fig | |
def chat_interface(query): | |
"""Process the user query and return text and plot.""" | |
# Parse input | |
dt, location, error = parse_query(query) | |
if error: | |
return error, None | |
# Get UTC time and coordinates | |
utc_dt, lat, lon, error = get_utc_time(dt, location) | |
if error: | |
return error, None | |
# Calculate planetary positions | |
result = PLadder_ZSizes(utc_dt, lat, lon) | |
if "error" in result: | |
return result["error"], None | |
# Format output | |
PLadder = result["PLadder"] | |
ZSizes = result["ZSizes"] | |
longitudes = result["longitudes"] | |
# Generate planet list text with full DMS | |
planet_list = "\n".join( | |
f"{PLANET_RU[p]}: {lon_to_sign(longitudes[p])}" | |
for p in PLadder | |
) | |
# Generate zone sizes text | |
zones_text = "\n".join( | |
f"Zone {i+1}: {size} ({cls})" | |
for i, (size, cls) in enumerate(ZSizes) | |
) | |
# Generate coordinates text | |
coords_text = format_coords(lat, lon) | |
# Create visualization (original style) | |
fig = plot_pladder(PLadder) | |
buf = BytesIO() | |
fig.savefig(buf, format='png', dpi=120, bbox_inches='tight') | |
buf.seek(0) | |
img = Image.open(buf) | |
plt.close(fig) | |
# Compose final output | |
output_text = ( | |
f"Planetary Ladder:\n{planet_list}\n\n" | |
f"Zone Sizes:\n{zones_text}\n\n" | |
f"Coordinates: {coords_text}\n" | |
f"Calculation Time: {utc_dt.strftime('%Y-%m-%d %H:%M:%S UTC')}" | |
) | |
return output_text, img | |
# Gradio Interface | |
with gr.Blocks(title="Planetary Ladder Calculator") as interface: | |
gr.Markdown("## Planetary Ladder Calculator") | |
with gr.Row(): | |
with gr.Column(scale=2): | |
output_text = gr.Textbox(label="Astrological Data", lines=12) | |
with gr.Column(scale=1): | |
output_image = gr.Image(label="Visualization") | |
with gr.Row(): | |
query_text = gr.Textbox( | |
label="Input Query", | |
placeholder="Example for Elon Mask: PLadder 28-06-1971 12:03:00 Pretoria", | |
max_lines=1 | |
) | |
with gr.Row(): | |
submit_button = gr.Button("Calculate", variant="primary") | |
submit_button.click( | |
fn=chat_interface, | |
inputs=query_text, | |
outputs=[output_text, output_image] | |
) | |
if __name__ == "__main__": | |
interface.launch() |