SergeyO7 commited on
Commit
d507774
·
verified ·
1 Parent(s): d2eb13d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +229 -124
app.py CHANGED
@@ -9,158 +9,200 @@ from timezonefinder import TimezoneFinder
9
  import pytz
10
  import swisseph as swe
11
 
 
 
12
 
13
  # Russian translations for planets
14
- planet_ru = {
15
- 'Sun': 'Солнце', 'Moon': 'Луна', 'Mercury': 'Меркурий', 'Venus': 'Венера',
16
- 'Mars': 'Марс', 'Jupiter': 'Юпитер', 'Saturn': 'Сатурн'
 
17
  }
18
 
19
  # Planet symbols for plotting
20
- planet_symbols = {
21
  'Sun': '☉', 'Moon': '☾', 'Mercury': '☿', 'Venus': '♀',
22
  'Mars': '♂', 'Jupiter': '♃', 'Saturn': '♄'
23
  }
24
 
 
 
 
 
 
25
 
26
  def parse_query(query):
27
- """Parse the query into date, time, and location."""
28
- parts = query.split()
29
- if len(parts) < 3:
30
- return None, None, "Insufficient information."
31
- date_str, time_str, *location_parts = parts
32
- location = " ".join(location_parts)
 
 
 
 
33
  try:
 
 
34
  dt = parser.parse(f"{date_str} {time_str}")
35
  return dt, location, None
36
- except ValueError:
37
- return None, None, "Invalid date or time format."
38
 
39
  def get_utc_time(dt, location):
40
- """Convert local time to UTC using location's time zone."""
41
- geolocator = Nominatim(user_agent="pladder")
 
 
 
 
 
 
 
42
  try:
43
- loc = geolocator.geocode(location)
44
  if not loc:
45
- return None, None, None, "Location not found."
 
46
  lat, lon = loc.latitude, loc.longitude
47
- tf = TimezoneFinder()
48
- tz_str = tf.timezone_at(lng=lon, lat=lat)
49
  if not tz_str:
50
- return None, None, None, "Time zone not found."
 
51
  tz = pytz.timezone(tz_str)
52
  local_dt = tz.localize(dt)
53
- utc_dt = local_dt.astimezone(pytz.utc)
54
  return utc_dt, lat, lon, None
 
55
  except Exception as e:
56
- return None, None, None, f"Error: {str(e)}"
57
 
58
  def format_coords(lat, lon):
59
- """Format coordinates as degrees, minutes, seconds."""
60
- def dms(value, pos, neg):
61
- dir = pos if value >= 0 else neg
62
- value = abs(value)
63
- d = int(value)
64
- m = int((value - d) * 60)
65
- s = int(((value - d) * 60 - m) * 60)
66
- return f"{d}°{m:02}'{s:02}\" {dir}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
  return f"{dms(lat, 'N', 'S')}, {dms(lon, 'E', 'W')}"
68
 
69
- def lon_to_sign(lon):
70
- """Convert longitude to zodiac sign format."""
71
- signs = ["Овен", "Телец", "Близнецы", "Рак", "Лев", "Дева",
72
- "Весы", "Скорпион", "Стрелец", "Козерог", "Водолей", "Рыбы"]
73
- sign_index = int(lon // 30)
74
- degrees = int(lon % 30)
75
- minutes = int((lon % 1) * 60)
76
- return f"{signs[sign_index]} {degrees}°{minutes}'"
 
 
 
 
 
 
 
 
 
 
 
77
 
78
  def PLadder_ZSizes(utc_dt, lat, lon):
79
- """Calculate Planetary Ladder and Zone Sizes using Swiss Ephemeris."""
80
-
81
- # Updated time range check (Swiss Ephemeris supports -13000 to +17000)
 
 
 
 
 
 
 
82
  if not -13000 <= utc_dt.year <= 17000:
83
- return {"error": "Date out of Swiss Ephemeris range (-13,000–17,000 CE)."}
84
 
85
- # Configure Swiss Ephemeris for high precision
86
- swe.set_ephe_path(None) # Use default ephemeris path
87
- swe.set_jpl_file('de441.eph') # Use DE441 for best modern accuracy (optional)
88
-
89
- # Planet mapping (Swiss Ephemeris constants)
90
- planet_objects = {
91
  'Sun': swe.SUN, 'Moon': swe.MOON, 'Mercury': swe.MERCURY,
92
  'Venus': swe.VENUS, 'Mars': swe.MARS,
93
  'Jupiter': swe.JUPITER, 'Saturn': swe.SATURN
94
  }
95
 
96
- # Convert datetime to Julian Day (high precision)
97
  jd_utc = swe.julday(
98
  utc_dt.year, utc_dt.month, utc_dt.day,
99
  utc_dt.hour + utc_dt.minute/60 + utc_dt.second/3600
100
  )
101
 
102
- # Calculate geocentric ecliptic longitudes (with light-time correction)
103
  longitudes = {}
104
- for planet, planet_id in planet_objects.items():
105
- flags = swe.FLG_SWIEPH | swe.FLG_SPEED # High-precision mode
106
  xx, _ = swe.calc_ut(jd_utc, planet_id, flags)
107
  lon = xx[0] % 360 # Normalize to 0-360°
108
-
109
- # Convert to degrees, minutes, seconds (DMS)
110
- d = int(lon)
111
- m = int((lon - d) * 60)
112
- s = round(((lon - d) * 60 - m) * 60)
113
-
114
- # Handle rounding (e.g., 59.999" → 60" → increment minute)
115
- if s >= 60:
116
- s -= 60
117
- m += 1
118
- if m >= 60:
119
- m -= 60
120
- d += 1
121
-
122
- longitudes[planet] = f"{d}°{m:02d}'{s:02d}\""
123
 
124
- # Sort planets by longitude (for PLadder and ZSizes)
125
- sorted_planets = sorted(
126
- longitudes.items(),
127
- key=lambda x: float(x[1].split('°')[0]) # Sort by degrees
128
- )
129
  PLadder = [p for p, _ in sorted_planets]
130
- sorted_lons = [float(lon.split('°')[0]) for _, lon in sorted_planets]
131
-
132
- # Calculate zone sizes (angular distances)
133
- zone_sizes = (
134
- [sorted_lons[0]] +
135
- [sorted_lons[i+1] - sorted_lons[i] for i in range(6)] +
136
- [360 - sorted_lons[6]]
137
- )
138
 
139
- # Classify zone sizes (with exact DMS)
140
- bordering = (
141
- [[PLadder[0]]] +
142
- [[PLadder[i-1], PLadder[i]] for i in range(1, 7)] +
143
- [[PLadder[6]]]
144
- )
145
 
 
146
  ZSizes = []
147
  for i, size in enumerate(zone_sizes):
148
- bord = bordering[i]
149
- X = (7 if any(p in ['Sun', 'Moon'] for p in bord)
150
- else 6 if any(p in ['Mercury', 'Venus', 'Mars'] for p in bord)
151
- else 5)
 
 
 
152
 
153
- # Exact DMS for zone size
 
 
 
 
 
 
 
 
154
  d = int(size)
155
  m = int((size - d) * 60)
156
- s = round(((size - d) * 60 - m) * 60)
 
 
157
  if s >= 60:
158
  s -= 60
159
  m += 1
160
  if m >= 60:
161
  m -= 60
162
  d += 1
163
-
164
  classification = (
165
  'Swallowed' if size <= 1 else
166
  'Tiny' if size <= X else
@@ -170,72 +212,135 @@ def PLadder_ZSizes(utc_dt, lat, lon):
170
  'Big'
171
  )
172
 
173
- ZSizes.append((f"{d}°{m:02d}'{s:02d}\"", classification))
174
 
175
  return {
176
  'PLadder': PLadder,
177
  'ZSizes': ZSizes,
178
- 'longitudes': longitudes # Now in DMS format (e.g., "12°59'55\"")
179
  }
180
 
181
  def plot_pladder(PLadder):
182
- fig, ax = plt.subplots()
183
- # Plot the triangle
184
- ax.plot([0, 1.5, 3, 0], [0, 3, 0, 0], 'k-')
185
- # Plot horizontal lines within the triangle
186
- ax.plot([0.5, 2.5], [1, 1], 'k--')
187
- ax.plot([1, 2], [2, 2], 'k--')
188
- # Define symbol positions outside the triangle
189
- 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)]
190
- # Plot planet symbols
191
- for i, (x, y) in enumerate(positions):
192
- ax.text(x, y, planet_symbols[PLadder[i]], ha='center', va='center', fontsize=24)
193
- # Set plot limits and aesthetics
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
194
  ax.set_xlim(-0.5, 3.5)
195
  ax.set_ylim(-0.5, 3.5)
196
  ax.set_aspect('equal')
197
  ax.axis('off')
 
 
198
  return fig
199
 
200
  def chat_interface(query):
201
- """Process the user query and return text and plot."""
202
- if not query.startswith("PLadder "):
203
- return "Query must start with 'PLadder' followed by date, time, and location.", None
204
- query_content = query.split(" ", 1)[1]
205
- dt, location, error = parse_query(query_content)
 
 
 
 
206
  if error:
207
  return error, None
 
 
208
  utc_dt, lat, lon, error = get_utc_time(dt, location)
209
  if error:
210
  return error, None
 
 
211
  result = PLadder_ZSizes(utc_dt, lat, lon)
212
  if "error" in result:
213
  return result["error"], None
 
 
214
  PLadder = result["PLadder"]
215
  ZSizes = result["ZSizes"]
216
  longitudes = result["longitudes"]
217
- planet_list = "\n".join([f"{planet_ru[p]}: {lon_to_sign(longitudes[p])}" for p in PLadder])
218
- zones_text = "\n".join([f"Zone {i+1}: {size} ({cls})" for i, (size, cls) in enumerate(ZSizes)])
 
 
 
 
 
 
 
 
 
 
 
 
219
  coords_text = format_coords(lat, lon)
 
 
220
  fig = plot_pladder(PLadder)
221
  buf = BytesIO()
222
- fig.savefig(buf, format='png', bbox_inches='tight')
223
  buf.seek(0)
224
  img = Image.open(buf)
225
  plt.close(fig)
226
- text = f"Planetary Ladder:\n{planet_list}\n\nZone Sizes:\n{zones_text}\n\nCoordinates: {coords_text}"
227
- return text, img
 
 
 
 
 
 
 
 
228
 
229
- # Gradio UI
230
- with gr.Blocks() as interface:
 
231
  with gr.Row():
232
  with gr.Column(scale=2):
233
- output_text = gr.Textbox(label="Response", lines=10)
234
  with gr.Column(scale=1):
235
- output_image = gr.Image(label="Planetary Ladder Plot")
 
 
 
 
 
 
236
  with gr.Row():
237
- query_text = gr.Textbox(label="Query", placeholder="Example: PLadder 2023-10-10 12:00 New York")
238
- submit_button = gr.Button("Submit")
239
- submit_button.click(fn=chat_interface, inputs=query_text, outputs=[output_text, output_image])
 
 
 
 
240
 
241
- interface.launch()
 
 
9
  import pytz
10
  import swisseph as swe
11
 
12
+ # Constants and Configuration
13
+ swe.set_ephe_path(None) # Initialize Swiss Ephemeris once at startup
14
 
15
  # Russian translations for planets
16
+ PLANET_RU = {
17
+ 'Sun': 'Солнце', 'Moon': 'Луна', 'Mercury': 'Меркурий',
18
+ 'Venus': 'Венера', 'Mars': 'Марс', 'Jupiter': 'Юпитер',
19
+ 'Saturn': 'Сатурн'
20
  }
21
 
22
  # Planet symbols for plotting
23
+ PLANET_SYMBOLS = {
24
  'Sun': '☉', 'Moon': '☾', 'Mercury': '☿', 'Venus': '♀',
25
  'Mars': '♂', 'Jupiter': '♃', 'Saturn': '♄'
26
  }
27
 
28
+ # Zodiac signs in Russian
29
+ ZODIAC_SIGNS = [
30
+ "Овен", "Телец", "Близнецы", "Рак", "Лев", "Дева",
31
+ "Весы", "Скорпион", "Стрелец", "Козерог", "Водолей", "Рыбы"
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
+ _, date_str, time_str, *location_parts = query.split(maxsplit=3)
47
+ location = location_parts[0] if location_parts else ""
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)
65
  if not loc:
66
+ return None, None, None, "Location not found"
67
+
68
  lat, lon = loc.latitude, loc.longitude
69
+ tz_str = TimezoneFinder().timezone_at(lng=lon, lat=lat)
 
70
  if not tz_str:
71
+ return None, None, None, "Time zone not found"
72
+
73
  tz = pytz.timezone(tz_str)
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"Geocoding error: {str(e)}"
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:
99
+ seconds -= 60
100
+ minutes += 1
101
+ if minutes >= 60:
102
+ minutes -= 60
103
+ degrees += 1
104
+
105
+ return f"{degrees}°{minutes:02}'{seconds:02}\" {direction}"
106
+
107
  return f"{dms(lat, 'N', 'S')}, {dms(lon, 'E', 'W')}"
108
 
109
+ def lon_to_sign(lon_deg):
110
+ """
111
+ Convert ecliptic longitude to zodiac sign with position.
112
+ Args:
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
 
143
+ # Planet mapping with Swiss Ephemeris constants
144
+ PLANET_OBJECTS = {
 
 
 
 
145
  'Sun': swe.SUN, 'Moon': swe.MOON, 'Mercury': swe.MERCURY,
146
  'Venus': swe.VENUS, 'Mars': swe.MARS,
147
  'Jupiter': swe.JUPITER, 'Saturn': swe.SATURN
148
  }
149
 
150
+ # Calculate Julian Day with high precision
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
154
  )
155
 
156
+ # Calculate planetary positions
157
  longitudes = {}
158
+ for planet, planet_id in PLANET_OBJECTS.items():
159
+ flags = swe.FLG_SWIEPH | swe.FLG_SPEED
160
  xx, _ = swe.calc_ut(jd_utc, planet_id, flags)
161
  lon = xx[0] % 360 # Normalize to 0-360°
162
+ longitudes[planet] = lon
 
 
 
 
 
 
 
 
 
 
 
 
 
 
163
 
164
+ # Sort planets by longitude
165
+ sorted_planets = sorted(longitudes.items(), key=lambda x: x[1])
 
 
 
166
  PLadder = [p for p, _ in sorted_planets]
167
+ sorted_lons = [lon for _, lon in sorted_planets]
 
 
 
 
 
 
 
168
 
169
+ # Calculate zone sizes
170
+ zone_sizes = [sorted_lons[0]] # First zone
171
+ zone_sizes.extend(sorted_lons[i+1] - sorted_lons[i] for i in range(6)) # Middle zones
172
+ zone_sizes.append(360 - sorted_lons[-1]) # Last zone
 
 
173
 
174
+ # Classify zone sizes
175
  ZSizes = []
176
  for i, size in enumerate(zone_sizes):
177
+ # Get bordering planets
178
+ if i == 0:
179
+ bord = [PLadder[0]]
180
+ elif i == len(zone_sizes)-1:
181
+ bord = [PLadder[-1]]
182
+ else:
183
+ bord = [PLadder[i-1], PLadder[i]]
184
 
185
+ # Determine zone classification
186
+ if any(p in ['Sun', 'Moon'] for p in bord):
187
+ X = 7
188
+ elif any(p in ['Mercury', 'Venus', 'Mars'] for p in bord):
189
+ X = 6
190
+ else:
191
+ X = 5
192
+
193
+ # Format size and classification
194
  d = int(size)
195
  m = int((size - d) * 60)
196
+ s = int(round(((size - d) * 60 - m) * 60))
197
+
198
+ # Handle rounding overflow
199
  if s >= 60:
200
  s -= 60
201
  m += 1
202
  if m >= 60:
203
  m -= 60
204
  d += 1
205
+
206
  classification = (
207
  'Swallowed' if size <= 1 else
208
  'Tiny' if size <= X else
 
212
  'Big'
213
  )
214
 
215
+ ZSizes.append((f"{d}°{m:02}'{s:02}\"", classification))
216
 
217
  return {
218
  'PLadder': PLadder,
219
  'ZSizes': ZSizes,
220
+ 'longitudes': {k: v for k, v in longitudes.items()} # Raw degrees
221
  }
222
 
223
  def plot_pladder(PLadder):
224
+ """
225
+ Generate visualization of the planetary ladder.
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--', alpha=0.5)
238
+ ax.plot([1, 2], [2, 2], 'k--', alpha=0.5)
239
+
240
+ # Planet symbol positions
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
+
246
+ # Add planet symbols
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, fontweight='bold')
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:
272
  return error, None
273
+
274
+ # Get UTC time and coordinates
275
  utc_dt, lat, lon, error = get_utc_time(dt, location)
276
  if error:
277
  return error, None
278
+
279
+ # Calculate planetary positions
280
  result = PLadder_ZSizes(utc_dt, lat, lon)
281
  if "error" in result:
282
  return result["error"], None
283
+
284
+ # Format output
285
  PLadder = result["PLadder"]
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
293
+ )
294
+
295
+ # Generate zone sizes text
296
+ zones_text = "\n".join(
297
+ f"Zone {i+1}: {size} ({cls})"
298
+ for i, (size, cls) in enumerate(ZSizes)
299
+ )
300
+
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')
308
  buf.seek(0)
309
  img = Image.open(buf)
310
  plt.close(fig)
311
+
312
+ # Compose final output
313
+ output_text = (
314
+ f"Planetary Ladder:\n{planet_list}\n\n"
315
+ f"Zone Sizes:\n{zones_text}\n\n"
316
+ f"Coordinates: {coords_text}\n"
317
+ f"Calculation Time: {utc_dt.strftime('%Y-%m-%d %H:%M:%S UTC')}"
318
+ )
319
+
320
+ return output_text, img
321
 
322
+ # Gradio Interface
323
+ with gr.Blocks(title="Planetary Ladder Calculator") as interface:
324
+ gr.Markdown("## Planetary Ladder Calculator")
325
  with gr.Row():
326
  with gr.Column(scale=2):
327
+ output_text = gr.Textbox(label="Astrological Data", lines=12)
328
  with gr.Column(scale=1):
329
+ output_image = gr.Image(label="Visualization")
330
+ with gr.Row():
331
+ query_text = gr.Textbox(
332
+ label="Input Query",
333
+ placeholder="Example: PLadder 2023-10-10 12:00 New York",
334
+ max_lines=1
335
+ )
336
  with gr.Row():
337
+ submit_button = gr.Button("Calculate", variant="primary")
338
+
339
+ submit_button.click(
340
+ fn=chat_interface,
341
+ inputs=query_text,
342
+ outputs=[output_text, output_image]
343
+ )
344
 
345
+ if __name__ == "__main__":
346
+ interface.launch()