diff --git a/.codex-temp/php-server.err.log b/.codex-temp/php-server.err.log index 8cb2a71..d677643 100644 --- a/.codex-temp/php-server.err.log +++ b/.codex-temp/php-server.err.log @@ -626,3 +626,27 @@ Stack trace: [Fri Apr 3 14:43:53 2026] 127.0.0.1:63593 Accepted [Fri Apr 3 14:43:55 2026] 127.0.0.1:63593 [200]: GET /favicon.ico [Fri Apr 3 14:43:55 2026] 127.0.0.1:63593 Closing +[Fri Apr 3 14:50:16 2026] 127.0.0.1:61254 Accepted +[Fri Apr 3 14:50:34 2026] 127.0.0.1:61254 [200]: GET /monatsvorhersage_old.php?year=2026&month=1 +[Fri Apr 3 14:50:34 2026] 127.0.0.1:61254 Closing +[Fri Apr 3 14:50:34 2026] 127.0.0.1:61256 Accepted +[Fri Apr 3 14:50:51 2026] 127.0.0.1:61256 [200]: GET /monatsvorhersage_old.php?year=2026&month=1 +[Fri Apr 3 14:50:51 2026] 127.0.0.1:61256 Closing +[Fri Apr 3 14:51:26 2026] 127.0.0.1:52317 Accepted +[Fri Apr 3 14:51:41 2026] 127.0.0.1:52317 [200]: GET /monatsvorhersage_old.php?year=2026&month=1 +[Fri Apr 3 14:51:41 2026] 127.0.0.1:52317 Closing +[Fri Apr 3 14:51:41 2026] 127.0.0.1:63047 Accepted +[Fri Apr 3 14:51:41 2026] 127.0.0.1:61026 Accepted +[Fri Apr 3 14:51:41 2026] 127.0.0.1:63047 [200]: GET /css/style.css +[Fri Apr 3 14:51:41 2026] 127.0.0.1:61026 [200]: GET /js/astronomy.browser.min.js +[Fri Apr 3 14:51:41 2026] 127.0.0.1:63047 Closing +[Fri Apr 3 14:51:41 2026] 127.0.0.1:61026 Closing +[Fri Apr 3 14:51:41 2026] 127.0.0.1:52369 Accepted +[Fri Apr 3 14:51:41 2026] 127.0.0.1:61642 Accepted +[Fri Apr 3 14:51:41 2026] 127.0.0.1:52369 [200]: GET /images/star16x16.png +[Fri Apr 3 14:51:41 2026] 127.0.0.1:61642 [200]: GET /images/star16x16_rays.png +[Fri Apr 3 14:51:41 2026] 127.0.0.1:52369 Closing +[Fri Apr 3 14:51:41 2026] 127.0.0.1:61642 Closing +[Fri Apr 3 14:51:41 2026] 127.0.0.1:57770 Accepted +[Fri Apr 3 14:51:43 2026] 127.0.0.1:57770 [200]: GET /favicon.ico +[Fri Apr 3 14:51:43 2026] 127.0.0.1:57770 Closing diff --git a/public/monatsvorhersage_old.php b/public/monatsvorhersage_old.php index ab041ff..9eec81e 100644 --- a/public/monatsvorhersage_old.php +++ b/public/monatsvorhersage_old.php @@ -64,6 +64,7 @@ $moonApsisEvents = []; $sunApsisEvents = []; $innerPlanetElongationEvents = []; $outerPlanetEvents = []; +$outerPlanetStationEvents = []; $moonOccultations = []; $seasonChanges = []; $monthEventList = []; @@ -440,6 +441,23 @@ if (is_array($outerPlanetDecoded) && ($outerPlanetDecoded['ok'] ?? false)) { $outerPlanetEvents = is_array($outerPlanetDecoded['events'] ?? null) ? $outerPlanetDecoded['events'] : []; } +$outerPlanetStationOutput = []; +$outerPlanetStationExitCode = 0; +$outerPlanetStationArgs = [ + $pythonScriptPath, + 'outer_planet_stations_for_month', + (string) $selectedYear, + (string) $selectedMonth, + $chartTimezone, +]; +$outerPlanetStationCommand = $pythonExecutable . ' ' . implode(' ', array_map('escapeshellarg', $outerPlanetStationArgs)) . ' 2>&1'; +exec($outerPlanetStationCommand, $outerPlanetStationOutput, $outerPlanetStationExitCode); +$outerPlanetStationRaw = trim(implode("\n", $outerPlanetStationOutput)); +$outerPlanetStationDecoded = json_decode($outerPlanetStationRaw, true); +if (is_array($outerPlanetStationDecoded) && ($outerPlanetStationDecoded['ok'] ?? false)) { + $outerPlanetStationEvents = is_array($outerPlanetStationDecoded['events'] ?? null) ? $outerPlanetStationDecoded['events'] : []; +} + $moonPlanetOutput = []; $moonPlanetExitCode = 0; $moonPlanetArgs = [ @@ -672,6 +690,28 @@ foreach ($outerPlanetEvents as $outerPlanetEvent) { ]; } +foreach ($outerPlanetStationEvents as $outerPlanetStationEvent) { + if (!is_array($outerPlanetStationEvent)) { + continue; + } + + $stationPlanet = (string) ($outerPlanetStationEvent['planet_label'] ?? 'Planet'); + $stationKind = (string) ($outerPlanetStationEvent['kind'] ?? ''); + $stationLabel = $stationKind === 'retrograde' + ? $stationPlanet . ' stationär, dann rückläufig' + : ($stationKind === 'prograde' + ? $stationPlanet . ' stationär, dann rechtläufig' + : (string) ($outerPlanetStationEvent['label'] ?? 'Stationärer Punkt')); + + $monthEventList[] = [ + 'event' => $stationLabel, + 'date' => (string) ($outerPlanetStationEvent['local_date'] ?? ''), + 'time' => (string) ($outerPlanetStationEvent['local_time'] ?? ''), + 'local_iso' => (string) ($outerPlanetStationEvent['local_iso'] ?? ''), + 'type' => 'outer_planet_station', + ]; +} + foreach ($moonPlanetApproaches as $approachEvent) { if (!is_array($approachEvent)) { continue; diff --git a/public/py/__pycache__/api.cpython-314.pyc b/public/py/__pycache__/api.cpython-314.pyc index ede6ca3..18f02cc 100644 Binary files a/public/py/__pycache__/api.cpython-314.pyc and b/public/py/__pycache__/api.cpython-314.pyc differ diff --git a/public/py/api.py b/public/py/api.py index 86b5d86..4e00831 100644 --- a/public/py/api.py +++ b/public/py/api.py @@ -1308,6 +1308,152 @@ def action_outer_planet_events_for_month(args: list[str]) -> dict: } +def geocentric_ecliptic_longitude_deg(body: astronomy.Body, dt_utc: datetime) -> float: + time_value = dt_to_time(dt_utc) + vector = astronomy.GeoVector(body, time_value, True) + ecliptic = astronomy.Ecliptic(vector) + return float(ecliptic.elon) + + +def longitude_delta_deg(left_deg: float, right_deg: float) -> float: + delta = right_deg - left_deg + while delta <= -180.0: + delta += 360.0 + while delta > 180.0: + delta -= 360.0 + return delta + + +def planet_longitude_rate_deg_per_day(body: astronomy.Body, dt_utc: datetime) -> float: + step = timedelta(hours=12) + left = geocentric_ecliptic_longitude_deg(body, dt_utc - step) + right = geocentric_ecliptic_longitude_deg(body, dt_utc + step) + return longitude_delta_deg(left, right) + + +def refine_stationary_time(body: astronomy.Body, left_utc: datetime, right_utc: datetime) -> datetime: + left = left_utc + right = right_utc + f_left = planet_longitude_rate_deg_per_day(body, left) + f_right = planet_longitude_rate_deg_per_day(body, right) + + for _ in range(40): + mid = left + (right - left) / 2 + f_mid = planet_longitude_rate_deg_per_day(body, mid) + if f_left == 0: + return left + if f_right == 0: + return right + if f_left * f_mid <= 0: + right = mid + f_right = f_mid + else: + left = mid + f_left = f_mid + + return left + (right - left) / 2 + + +def action_outer_planet_stations_for_month(args: list[str]) -> dict: + if len(args) != 3: + fail( + "Aktion outer_planet_stations_for_month erwartet 3 Argumente: year month timezone", + extra={"argv": args}, + ) + + try: + year = int(args[0]) + month = int(args[1]) + except ValueError as exc: + fail("Jahr oder Monat sind ungueltig.", extra={"details": str(exc), "argv": args}) + + if month < 1 or month > 12: + fail("Monat muss zwischen 1 und 12 liegen.", extra={"month": month}) + + timezone_name = args[2] + try: + tz = ZoneInfo(timezone_name) + except Exception as exc: + fail("Zeitzone ist ungueltig oder auf dem Server nicht verfuegbar.", extra={"details": str(exc)}) + + local_start = datetime(year, month, 1, 0, 0, 0, tzinfo=tz) + if month == 12: + local_end = datetime(year + 1, 1, 1, 0, 0, 0, tzinfo=tz) + else: + local_end = datetime(year, month + 1, 1, 0, 0, 0, tzinfo=tz) + + utc_start = local_start.astimezone(timezone.utc) + utc_end = local_end.astimezone(timezone.utc) + scan_start = utc_start - timedelta(days=70) + scan_end = utc_end + timedelta(days=70) + scan_step = timedelta(hours=12) + + planet_defs = [ + ("Mars", astronomy.Body.Mars, "mars"), + ("Jupiter", astronomy.Body.Jupiter, "jupiter"), + ("Saturn", astronomy.Body.Saturn, "saturn"), + ("Uranus", astronomy.Body.Uranus, "uranus"), + ("Neptun", astronomy.Body.Neptune, "neptune"), + ] + + events: list[dict] = [] + for label, body, key in planet_defs: + current = scan_start + previous_time = current + previous_rate = planet_longitude_rate_deg_per_day(body, current) + current += scan_step + + while current <= scan_end: + current_rate = planet_longitude_rate_deg_per_day(body, current) + if previous_rate == 0 or current_rate == 0 or previous_rate * current_rate < 0: + station_time_utc = refine_stationary_time(body, previous_time, current) + local_dt = station_time_utc.astimezone(tz) + if local_start <= local_dt < local_end: + rate_before = planet_longitude_rate_deg_per_day(body, station_time_utc - timedelta(days=3)) + rate_after = planet_longitude_rate_deg_per_day(body, station_time_utc + timedelta(days=3)) + if rate_before > 0 and rate_after < 0: + kind = "retrograde" + text = "stationaer, dann ruecklaeufig" + elif rate_before < 0 and rate_after > 0: + kind = "prograde" + text = "stationaer, dann rechtlaeufig" + else: + kind = "stationary" + text = "stationaer" + + if not any( + item["planet_key"] == key and abs(datetime.fromisoformat(item["local_iso"]).timestamp() - local_dt.timestamp()) < 3600 + for item in events + ): + events.append({ + "planet_key": key, + "planet_label": label, + "kind": kind, + "label": f"{label} {text}", + "utc_iso": station_time_utc.isoformat().replace("+00:00", "Z"), + "local_iso": local_dt.isoformat(), + "local_date": local_dt.strftime("%d.%m.%Y"), + "local_time": local_dt.strftime("%H:%M"), + }) + + previous_time = current + previous_rate = current_rate + current += scan_step + + events.sort(key=lambda item: item["local_iso"]) + + return { + "ok": True, + "action": "outer_planet_stations_for_month", + "selected": { + "year": year, + "month": month, + "timezone": timezone_name, + }, + "events": events, + } + + def action_moon_star_occultations_for_month(args: list[str]) -> dict: if len(args) != 7: fail( @@ -2364,7 +2510,7 @@ def action_satellite_passes(args: list[str]) -> dict: def main() -> None: if len(sys.argv) < 2: - fail("Es wurde keine Aktion uebergeben.", extra={"available_actions": ["sun_moon_rise_set", "moon_star_occultations", "moon_phase_details", "satellite_passes", "planet_rise_set", "month_sky_context", "moon_phases_for_month", "season_changes_for_month", "moon_planet_approaches", "planet_conjunctions_for_month", "eclipses_for_month", "moon_apsides_for_month", "sun_apsides_for_month", "inner_planet_elongations_for_month", "outer_planet_events_for_month", "moon_star_occultations_for_month"]}) + fail("Es wurde keine Aktion uebergeben.", extra={"available_actions": ["sun_moon_rise_set", "moon_star_occultations", "moon_phase_details", "satellite_passes", "planet_rise_set", "month_sky_context", "moon_phases_for_month", "season_changes_for_month", "moon_planet_approaches", "planet_conjunctions_for_month", "eclipses_for_month", "moon_apsides_for_month", "sun_apsides_for_month", "inner_planet_elongations_for_month", "outer_planet_events_for_month", "outer_planet_stations_for_month", "moon_star_occultations_for_month"]}) action = sys.argv[1] args = sys.argv[2:] @@ -2444,12 +2590,17 @@ def main() -> None: print(json.dumps(result, ensure_ascii=False)) return + if action == "outer_planet_stations_for_month": + result = action_outer_planet_stations_for_month(args) + print(json.dumps(result, ensure_ascii=False)) + return + if action == "moon_star_occultations_for_month": result = action_moon_star_occultations_for_month(args) print(json.dumps(result, ensure_ascii=False)) return - fail("Unbekannte Aktion.", extra={"action": action, "available_actions": ["sun_moon_rise_set", "moon_star_occultations", "moon_phase_details", "satellite_passes", "planet_rise_set", "month_sky_context", "moon_phases_for_month", "season_changes_for_month", "moon_planet_approaches", "planet_conjunctions_for_month", "eclipses_for_month", "moon_apsides_for_month", "sun_apsides_for_month", "inner_planet_elongations_for_month", "outer_planet_events_for_month", "moon_star_occultations_for_month"]}) + fail("Unbekannte Aktion.", extra={"action": action, "available_actions": ["sun_moon_rise_set", "moon_star_occultations", "moon_phase_details", "satellite_passes", "planet_rise_set", "month_sky_context", "moon_phases_for_month", "season_changes_for_month", "moon_planet_approaches", "planet_conjunctions_for_month", "eclipses_for_month", "moon_apsides_for_month", "sun_apsides_for_month", "inner_planet_elongations_for_month", "outer_planet_events_for_month", "outer_planet_stations_for_month", "moon_star_occultations_for_month"]}) if __name__ == "__main__":