Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -9,14 +9,14 @@ from timezonefinder import TimezoneFinder
|
|
| 9 |
import pytz
|
| 10 |
import swisseph as swe
|
| 11 |
|
| 12 |
-
#
|
| 13 |
-
swe.set_ephe_path(None)
|
| 14 |
|
| 15 |
# Russian translations for planets
|
| 16 |
PLANET_RU = {
|
| 17 |
-
'Sun': 'Солнце', 'Moon': 'Луна', 'Mercury': 'Меркурий',
|
| 18 |
-
'Venus': 'Венера', 'Mars': 'Марс',
|
| 19 |
-
'Saturn': 'Сатурн'
|
| 20 |
}
|
| 21 |
|
| 22 |
# Planet symbols for plotting
|
|
@@ -32,33 +32,23 @@ ZODIAC_SIGNS = [
|
|
| 32 |
]
|
| 33 |
|
| 34 |
def parse_query(query):
|
| 35 |
-
"""
|
| 36 |
-
Parse the query into date, time, and location.
|
| 37 |
-
Args:
|
| 38 |
-
query: String in format "PLadder YYYY-MM-DD HH:MM Location"
|
| 39 |
-
Returns:
|
| 40 |
-
tuple: (datetime, location, error_message)
|
| 41 |
-
"""
|
| 42 |
if not query.startswith("PLadder "):
|
| 43 |
return None, None, "Query must start with 'PLadder'"
|
| 44 |
|
| 45 |
try:
|
| 46 |
-
|
| 47 |
-
|
|
|
|
|
|
|
|
|
|
| 48 |
dt = parser.parse(f"{date_str} {time_str}")
|
| 49 |
return dt, location, None
|
| 50 |
except ValueError as e:
|
| 51 |
return None, None, f"Invalid format: {str(e)}"
|
| 52 |
|
| 53 |
def get_utc_time(dt, location):
|
| 54 |
-
"""
|
| 55 |
-
Convert local time to UTC using location's time zone.
|
| 56 |
-
Args:
|
| 57 |
-
dt: Local datetime
|
| 58 |
-
location: String location
|
| 59 |
-
Returns:
|
| 60 |
-
tuple: (utc_dt, lat, lon, error_message)
|
| 61 |
-
"""
|
| 62 |
geolocator = Nominatim(user_agent="pladder_app")
|
| 63 |
try:
|
| 64 |
loc = geolocator.geocode(location, timeout=10)
|
|
@@ -74,25 +64,17 @@ def get_utc_time(dt, location):
|
|
| 74 |
local_dt = tz.localize(dt)
|
| 75 |
utc_dt = local_dt.astimezone(pytz.UTC)
|
| 76 |
return utc_dt, lat, lon, None
|
| 77 |
-
|
| 78 |
except Exception as e:
|
| 79 |
-
return None, None, None, f"
|
| 80 |
|
| 81 |
def format_coords(lat, lon):
|
| 82 |
-
"""
|
| 83 |
-
Format coordinates as degrees, minutes, seconds.
|
| 84 |
-
Args:
|
| 85 |
-
lat: Latitude in degrees
|
| 86 |
-
lon: Longitude in degrees
|
| 87 |
-
Returns:
|
| 88 |
-
str: Formatted coordinates (e.g., "12°34'56" N, 98°45'32" E")
|
| 89 |
-
"""
|
| 90 |
def dms(value, pos_dir, neg_dir):
|
| 91 |
direction = pos_dir if value >= 0 else neg_dir
|
| 92 |
abs_value = abs(value)
|
| 93 |
degrees = int(abs_value)
|
| 94 |
minutes = int((abs_value - degrees) * 60)
|
| 95 |
-
seconds = round(((abs_value - degrees) * 60 - minutes) * 60)
|
| 96 |
|
| 97 |
# Handle rounding overflow
|
| 98 |
if seconds >= 60:
|
|
@@ -109,34 +91,26 @@ def format_coords(lat, lon):
|
|
| 109 |
def lon_to_sign(lon_deg):
|
| 110 |
"""
|
| 111 |
Convert ecliptic longitude to zodiac sign with position.
|
| 112 |
-
|
| 113 |
-
lon_deg: Longitude in degrees (0-360)
|
| 114 |
-
Returns:
|
| 115 |
-
str: Formatted sign and position (e.g., "Лев 12°34'")
|
| 116 |
"""
|
| 117 |
sign_idx = int(lon_deg // 30)
|
| 118 |
degrees_in_sign = lon_deg % 30
|
| 119 |
degrees = int(degrees_in_sign)
|
| 120 |
minutes = int((degrees_in_sign - degrees) * 60)
|
|
|
|
| 121 |
|
| 122 |
-
# Handle rounding
|
|
|
|
|
|
|
|
|
|
| 123 |
if minutes >= 60:
|
| 124 |
minutes -= 60
|
| 125 |
degrees += 1
|
| 126 |
|
| 127 |
-
return f"{ZODIAC_SIGNS[sign_idx]} {degrees}°{minutes:02}'"
|
| 128 |
|
| 129 |
def PLadder_ZSizes(utc_dt, lat, lon):
|
| 130 |
-
"""
|
| 131 |
-
Calculate Planetary Ladder and Zone Sizes using Swiss Ephemeris.
|
| 132 |
-
Args:
|
| 133 |
-
utc_dt: UTC datetime
|
| 134 |
-
lat: Latitude in degrees
|
| 135 |
-
lon: Longitude in degrees
|
| 136 |
-
Returns:
|
| 137 |
-
dict: Contains PLadder, ZSizes, and longitudes
|
| 138 |
-
"""
|
| 139 |
-
# Validate date range
|
| 140 |
if not -13000 <= utc_dt.year <= 17000:
|
| 141 |
return {"error": "Date out of supported range (-13,000–17,000 CE)"}
|
| 142 |
|
|
@@ -147,7 +121,7 @@ def PLadder_ZSizes(utc_dt, lat, lon):
|
|
| 147 |
'Jupiter': swe.JUPITER, 'Saturn': swe.SATURN
|
| 148 |
}
|
| 149 |
|
| 150 |
-
# Calculate Julian Day
|
| 151 |
jd_utc = swe.julday(
|
| 152 |
utc_dt.year, utc_dt.month, utc_dt.day,
|
| 153 |
utc_dt.hour + utc_dt.minute/60 + utc_dt.second/3600
|
|
@@ -190,7 +164,7 @@ def PLadder_ZSizes(utc_dt, lat, lon):
|
|
| 190 |
else:
|
| 191 |
X = 5
|
| 192 |
|
| 193 |
-
# Format size
|
| 194 |
d = int(size)
|
| 195 |
m = int((size - d) * 60)
|
| 196 |
s = int(round(((size - d) * 60 - m) * 60))
|
|
@@ -217,29 +191,23 @@ def PLadder_ZSizes(utc_dt, lat, lon):
|
|
| 217 |
return {
|
| 218 |
'PLadder': PLadder,
|
| 219 |
'ZSizes': ZSizes,
|
| 220 |
-
'longitudes':
|
| 221 |
}
|
| 222 |
|
| 223 |
def plot_pladder(PLadder):
|
| 224 |
-
"""
|
| 225 |
-
|
| 226 |
-
Args:
|
| 227 |
-
PLadder: Ordered list of planets
|
| 228 |
-
Returns:
|
| 229 |
-
matplotlib Figure
|
| 230 |
-
"""
|
| 231 |
-
fig, ax = plt.subplots(figsize=(8, 8))
|
| 232 |
|
| 233 |
-
# Draw main triangle
|
| 234 |
ax.plot([0, 1.5, 3, 0], [0, 3, 0, 0], 'k-', linewidth=2)
|
| 235 |
|
| 236 |
-
# Draw horizontal divisions
|
| 237 |
-
ax.plot([0.5, 2.5], [1, 1], 'k--'
|
| 238 |
-
ax.plot([1, 2], [2, 2], 'k--'
|
| 239 |
|
| 240 |
-
#
|
| 241 |
symbol_positions = [
|
| 242 |
-
(-0.2, 0.2), (0.3, 1.2), (0.8, 2.2),
|
| 243 |
(1.5, 3.2), (2.2, 2.2), (2.7, 1.2), (3.2, 0.2)
|
| 244 |
]
|
| 245 |
|
|
@@ -247,25 +215,18 @@ def plot_pladder(PLadder):
|
|
| 247 |
for (x, y), planet in zip(symbol_positions, PLadder):
|
| 248 |
ax.text(x, y, PLANET_SYMBOLS[planet],
|
| 249 |
ha='center', va='center',
|
| 250 |
-
fontsize=24
|
| 251 |
|
| 252 |
-
# Configure plot appearance
|
| 253 |
ax.set_xlim(-0.5, 3.5)
|
| 254 |
ax.set_ylim(-0.5, 3.5)
|
| 255 |
ax.set_aspect('equal')
|
| 256 |
ax.axis('off')
|
| 257 |
-
plt.tight_layout()
|
| 258 |
|
| 259 |
return fig
|
| 260 |
|
| 261 |
def chat_interface(query):
|
| 262 |
-
"""
|
| 263 |
-
Main processing function for the Gradio interface.
|
| 264 |
-
Args:
|
| 265 |
-
query: User input string
|
| 266 |
-
Returns:
|
| 267 |
-
tuple: (text_response, image)
|
| 268 |
-
"""
|
| 269 |
# Parse input
|
| 270 |
dt, location, error = parse_query(query)
|
| 271 |
if error:
|
|
@@ -286,7 +247,7 @@ def chat_interface(query):
|
|
| 286 |
ZSizes = result["ZSizes"]
|
| 287 |
longitudes = result["longitudes"]
|
| 288 |
|
| 289 |
-
# Generate planet list text
|
| 290 |
planet_list = "\n".join(
|
| 291 |
f"{PLANET_RU[p]}: {lon_to_sign(longitudes[p])}"
|
| 292 |
for p in PLadder
|
|
@@ -301,7 +262,7 @@ def chat_interface(query):
|
|
| 301 |
# Generate coordinates text
|
| 302 |
coords_text = format_coords(lat, lon)
|
| 303 |
|
| 304 |
-
# Create visualization
|
| 305 |
fig = plot_pladder(PLadder)
|
| 306 |
buf = BytesIO()
|
| 307 |
fig.savefig(buf, format='png', dpi=120, bbox_inches='tight')
|
|
|
|
| 9 |
import pytz
|
| 10 |
import swisseph as swe
|
| 11 |
|
| 12 |
+
# Initialize Swiss Ephemeris
|
| 13 |
+
swe.set_ephe_path(None)
|
| 14 |
|
| 15 |
# Russian translations for planets
|
| 16 |
PLANET_RU = {
|
| 17 |
+
'Sun': 'Солнце', 'Moon': 'Луна', 'Mercury': 'Меркурий',
|
| 18 |
+
'Venus': 'Венера', 'Mars': 'Марс',
|
| 19 |
+
'Jupiter': 'Юпитер', 'Saturn': 'Сатурн'
|
| 20 |
}
|
| 21 |
|
| 22 |
# Planet symbols for plotting
|
|
|
|
| 32 |
]
|
| 33 |
|
| 34 |
def parse_query(query):
|
| 35 |
+
"""Parse the query into date, time, and location."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
if not query.startswith("PLadder "):
|
| 37 |
return None, None, "Query must start with 'PLadder'"
|
| 38 |
|
| 39 |
try:
|
| 40 |
+
parts = query.split(maxsplit=3)
|
| 41 |
+
if len(parts) < 4:
|
| 42 |
+
return None, None, "Incomplete query (need date, time, and location)"
|
| 43 |
+
|
| 44 |
+
_, date_str, time_str, location = parts
|
| 45 |
dt = parser.parse(f"{date_str} {time_str}")
|
| 46 |
return dt, location, None
|
| 47 |
except ValueError as e:
|
| 48 |
return None, None, f"Invalid format: {str(e)}"
|
| 49 |
|
| 50 |
def get_utc_time(dt, location):
|
| 51 |
+
"""Convert local time to UTC using location's time zone."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
geolocator = Nominatim(user_agent="pladder_app")
|
| 53 |
try:
|
| 54 |
loc = geolocator.geocode(location, timeout=10)
|
|
|
|
| 64 |
local_dt = tz.localize(dt)
|
| 65 |
utc_dt = local_dt.astimezone(pytz.UTC)
|
| 66 |
return utc_dt, lat, lon, None
|
|
|
|
| 67 |
except Exception as e:
|
| 68 |
+
return None, None, None, f"Error: {str(e)}"
|
| 69 |
|
| 70 |
def format_coords(lat, lon):
|
| 71 |
+
"""Format coordinates as degrees, minutes, seconds."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
def dms(value, pos_dir, neg_dir):
|
| 73 |
direction = pos_dir if value >= 0 else neg_dir
|
| 74 |
abs_value = abs(value)
|
| 75 |
degrees = int(abs_value)
|
| 76 |
minutes = int((abs_value - degrees) * 60)
|
| 77 |
+
seconds = int(round(((abs_value - degrees) * 60 - minutes) * 60)
|
| 78 |
|
| 79 |
# Handle rounding overflow
|
| 80 |
if seconds >= 60:
|
|
|
|
| 91 |
def lon_to_sign(lon_deg):
|
| 92 |
"""
|
| 93 |
Convert ecliptic longitude to zodiac sign with position.
|
| 94 |
+
Now includes seconds in the output.
|
|
|
|
|
|
|
|
|
|
| 95 |
"""
|
| 96 |
sign_idx = int(lon_deg // 30)
|
| 97 |
degrees_in_sign = lon_deg % 30
|
| 98 |
degrees = int(degrees_in_sign)
|
| 99 |
minutes = int((degrees_in_sign - degrees) * 60)
|
| 100 |
+
seconds = int(round(((degrees_in_sign - degrees) * 60 - minutes) * 60)
|
| 101 |
|
| 102 |
+
# Handle rounding overflow
|
| 103 |
+
if seconds >= 60:
|
| 104 |
+
seconds -= 60
|
| 105 |
+
minutes += 1
|
| 106 |
if minutes >= 60:
|
| 107 |
minutes -= 60
|
| 108 |
degrees += 1
|
| 109 |
|
| 110 |
+
return f"{ZODIAC_SIGNS[sign_idx]} {degrees}°{minutes:02}'{seconds:02}\""
|
| 111 |
|
| 112 |
def PLadder_ZSizes(utc_dt, lat, lon):
|
| 113 |
+
"""Calculate Planetary Ladder and Zone Sizes using Swiss Ephemeris."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 114 |
if not -13000 <= utc_dt.year <= 17000:
|
| 115 |
return {"error": "Date out of supported range (-13,000–17,000 CE)"}
|
| 116 |
|
|
|
|
| 121 |
'Jupiter': swe.JUPITER, 'Saturn': swe.SATURN
|
| 122 |
}
|
| 123 |
|
| 124 |
+
# Calculate Julian Day
|
| 125 |
jd_utc = swe.julday(
|
| 126 |
utc_dt.year, utc_dt.month, utc_dt.day,
|
| 127 |
utc_dt.hour + utc_dt.minute/60 + utc_dt.second/3600
|
|
|
|
| 164 |
else:
|
| 165 |
X = 5
|
| 166 |
|
| 167 |
+
# Format size with seconds
|
| 168 |
d = int(size)
|
| 169 |
m = int((size - d) * 60)
|
| 170 |
s = int(round(((size - d) * 60 - m) * 60))
|
|
|
|
| 191 |
return {
|
| 192 |
'PLadder': PLadder,
|
| 193 |
'ZSizes': ZSizes,
|
| 194 |
+
'longitudes': longitudes # Raw degrees for calculations
|
| 195 |
}
|
| 196 |
|
| 197 |
def plot_pladder(PLadder):
|
| 198 |
+
"""Generate the original version of the planetary ladder visualization."""
|
| 199 |
+
fig, ax = plt.subplots(figsize=(6, 6))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 200 |
|
| 201 |
+
# Draw the main triangle
|
| 202 |
ax.plot([0, 1.5, 3, 0], [0, 3, 0, 0], 'k-', linewidth=2)
|
| 203 |
|
| 204 |
+
# Draw horizontal divisions (original style)
|
| 205 |
+
ax.plot([0.5, 2.5], [1, 1], 'k--')
|
| 206 |
+
ax.plot([1, 2], [2, 2], 'k--')
|
| 207 |
|
| 208 |
+
# Original planet symbol positions
|
| 209 |
symbol_positions = [
|
| 210 |
+
(-0.2, 0.2), (0.3, 1.2), (0.8, 2.2),
|
| 211 |
(1.5, 3.2), (2.2, 2.2), (2.7, 1.2), (3.2, 0.2)
|
| 212 |
]
|
| 213 |
|
|
|
|
| 215 |
for (x, y), planet in zip(symbol_positions, PLadder):
|
| 216 |
ax.text(x, y, PLANET_SYMBOLS[planet],
|
| 217 |
ha='center', va='center',
|
| 218 |
+
fontsize=24)
|
| 219 |
|
| 220 |
+
# Configure plot appearance (original style)
|
| 221 |
ax.set_xlim(-0.5, 3.5)
|
| 222 |
ax.set_ylim(-0.5, 3.5)
|
| 223 |
ax.set_aspect('equal')
|
| 224 |
ax.axis('off')
|
|
|
|
| 225 |
|
| 226 |
return fig
|
| 227 |
|
| 228 |
def chat_interface(query):
|
| 229 |
+
"""Process the user query and return text and plot."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 230 |
# Parse input
|
| 231 |
dt, location, error = parse_query(query)
|
| 232 |
if error:
|
|
|
|
| 247 |
ZSizes = result["ZSizes"]
|
| 248 |
longitudes = result["longitudes"]
|
| 249 |
|
| 250 |
+
# Generate planet list text with full DMS
|
| 251 |
planet_list = "\n".join(
|
| 252 |
f"{PLANET_RU[p]}: {lon_to_sign(longitudes[p])}"
|
| 253 |
for p in PLadder
|
|
|
|
| 262 |
# Generate coordinates text
|
| 263 |
coords_text = format_coords(lat, lon)
|
| 264 |
|
| 265 |
+
# Create visualization (original style)
|
| 266 |
fig = plot_pladder(PLadder)
|
| 267 |
buf = BytesIO()
|
| 268 |
fig.savefig(buf, format='png', dpi=120, bbox_inches='tight')
|