From de0c700f01e47bb3a686450b52f26f5bfa4ca366 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Sat, 4 Apr 2026 11:10:44 +0200 Subject: [PATCH] Planeten helle Sterne begegnung rein --- public/monatsvorhersage_old.php | 43 +++++ public/py/__pycache__/api.cpython-312.pyc | Bin 155540 -> 162863 bytes public/py/api.py | 198 +++++++++++++++++++++- 3 files changed, 239 insertions(+), 2 deletions(-) diff --git a/public/monatsvorhersage_old.php b/public/monatsvorhersage_old.php index 927dc6c..0e50702 100644 --- a/public/monatsvorhersage_old.php +++ b/public/monatsvorhersage_old.php @@ -18,6 +18,9 @@ $messierDatabaseMagnitudeLimit = 4.0; $messierDisplayMagnitudeLimit = 1.0; $moonPlanetMaxSeparationDeg = 1.5; $moonDeepSkyMaxSeparationDeg = 1.0; +$planetBrightStarMaxSeparationDeg = 1.0; +// Maximaler Winkelabstand fuer Begegnungen zwischen hellen Planeten und +// ausgewaehlten hellen Sternen wie Regulus oder Spica. $planetConjunctionMaxSeparationDeg = 1.0; $moonOccultationMaxMag = 2.0; // Gute Merkur-Sichtbarkeit wird nicht nur über Elongation, sondern über echte @@ -78,6 +81,7 @@ $moonPhaseEvents = []; $goldenHandleEvents = []; $moonPlanetApproaches = []; $moonDeepSkyApproaches = []; +$planetBrightStarApproaches = []; $mercuryGoodVisibilityEvents = []; $planetConjunctions = []; $eclipseEvents = []; @@ -696,6 +700,27 @@ if (is_array($moonDeepSkyDecoded) && ($moonDeepSkyDecoded['ok'] ?? false)) { $moonDeepSkyApproaches = is_array($moonDeepSkyDecoded['approaches'] ?? null) ? $moonDeepSkyDecoded['approaches'] : []; } +$planetBrightStarOutput = []; +$planetBrightStarExitCode = 0; +$planetBrightStarArgs = [ + $pythonScriptPath, + 'planet_bright_star_approaches_for_month', + (string) $chartLatitude, + (string) $chartLongitude, + (string) $chartElevation, + (string) $selectedYear, + (string) $selectedMonth, + $chartTimezone, + (string) $planetBrightStarMaxSeparationDeg, +]; +$planetBrightStarCommand = $pythonExecutable . ' ' . implode(' ', array_map('escapeshellarg', $planetBrightStarArgs)) . ' 2>&1'; +exec($planetBrightStarCommand, $planetBrightStarOutput, $planetBrightStarExitCode); +$planetBrightStarRaw = trim(implode("\n", $planetBrightStarOutput)); +$planetBrightStarDecoded = json_decode($planetBrightStarRaw, true); +if (is_array($planetBrightStarDecoded) && ($planetBrightStarDecoded['ok'] ?? false)) { + $planetBrightStarApproaches = is_array($planetBrightStarDecoded['approaches'] ?? null) ? $planetBrightStarDecoded['approaches'] : []; +} + $planetConjunctionOutput = []; $planetConjunctionExitCode = 0; $planetConjunctionArgs = [ @@ -1069,6 +1094,24 @@ foreach ($moonDeepSkyApproaches as $approachEvent) { ]; } +foreach ($planetBrightStarApproaches as $approachEvent) { + if (!is_array($approachEvent)) { + continue; + } + + $distanceText = isset($approachEvent['separation_deg']) + ? number_format((float) ($approachEvent['separation_deg']), 2, ',', '') . ' Grad' + : ''; + + $monthEventList[] = [ + 'event' => trim((string) ($approachEvent['label'] ?? 'Planet nahe hellem Stern') . ($distanceText !== '' ? ' (' . $distanceText . ')' : '')), + 'date' => (string) ($approachEvent['local_date'] ?? ''), + 'time' => (string) ($approachEvent['local_time'] ?? ''), + 'local_iso' => (string) ($approachEvent['local_iso'] ?? ''), + 'type' => 'planet_bright_star', + ]; +} + foreach ($planetConjunctions as $conjunctionEvent) { if (!is_array($conjunctionEvent)) { continue; diff --git a/public/py/__pycache__/api.cpython-312.pyc b/public/py/__pycache__/api.cpython-312.pyc index f132f926b89f2d0a31bbbf3336ac282650f77635..5ebe79b969c38c9e1df41e404f6594ffff1c38a4 100644 GIT binary patch delta 11043 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 delta 7386 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 arO=qe7u6jJYyYv?ws1IINzGBlD)~2LFNOO6 diff --git a/public/py/api.py b/public/py/api.py index 265c9ba..5d265d5 100644 --- a/public/py/api.py +++ b/public/py/api.py @@ -950,6 +950,18 @@ def planet_pair_separation_deg( return spherical_separation_deg(float(eq_a.ra), float(eq_a.dec), float(eq_b.ra), float(eq_b.dec)) +def planet_fixed_equatorial_separation_deg( + body: astronomy.Body, + ra_hours: float, + dec_deg: float, + observer: astronomy.Observer, + dt_utc: datetime, +) -> float: + time_value = dt_to_time(dt_utc) + body_eq = astronomy.Equator(body, time_value, observer, True, True) + return spherical_separation_deg(float(body_eq.ra), float(body_eq.dec), float(ra_hours), float(dec_deg)) + + def refine_minimum_separation( body: astronomy.Body, observer: astronomy.Observer, @@ -1024,6 +1036,32 @@ def refine_fixed_target_minimum_separation( return best, moon_fixed_equatorial_separation_deg(ra_hours, dec_deg, observer, best) +def refine_planet_fixed_target_minimum_separation( + body: astronomy.Body, + ra_hours: float, + dec_deg: float, + observer: astronomy.Observer, + left_utc: datetime, + right_utc: datetime, +) -> tuple[datetime, float]: + left = left_utc + right = right_utc + + for _ in range(32): + span = (right - left) / 3 + m1 = left + span + m2 = right - span + f1 = planet_fixed_equatorial_separation_deg(body, ra_hours, dec_deg, observer, m1) + f2 = planet_fixed_equatorial_separation_deg(body, ra_hours, dec_deg, observer, m2) + if f1 <= f2: + right = m2 + else: + left = m1 + + best = left + (right - left) / 2 + return best, planet_fixed_equatorial_separation_deg(body, ra_hours, dec_deg, observer, best) + + def action_moon_planet_approaches(args: list[str]) -> dict: if len(args) != 7: fail( @@ -1135,6 +1173,157 @@ def action_moon_planet_approaches(args: list[str]) -> dict: } +def action_planet_bright_star_approaches_for_month(args: list[str]) -> dict: + if len(args) != 7: + fail( + "Aktion planet_bright_star_approaches_for_month erwartet 7 Argumente: latitude longitude elevation year month timezone max_sep_deg", + extra={"argv": args}, + ) + + latitude = parse_float(args[0], "Latitude") + longitude = parse_float(args[1], "Longitude") + elevation = parse_float(args[2], "Elevation") + + try: + year = int(args[3]) + month = int(args[4]) + 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[5] + max_sep_deg = parse_float(args[6], "Maximalabstand") + + try: + tz = ZoneInfo(timezone_name) + except Exception as exc: + fail("Zeitzone ist ungueltig oder auf dem Server nicht verfuegbar.", extra={"details": str(exc)}) + + observer = astronomy.Observer(latitude, longitude, elevation) + 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_step = timedelta(hours=1) + coarse_threshold = max_sep_deg + 1.0 + + planet_defs = [ + ("Merkur", astronomy.Body.Mercury, "mercury"), + ("Venus", astronomy.Body.Venus, "venus"), + ("Mars", astronomy.Body.Mars, "mars"), + ("Jupiter", astronomy.Body.Jupiter, "jupiter"), + ("Saturn", astronomy.Body.Saturn, "saturn"), + ] + + # Helle, auffaellige Sterne nahe der Ekliptik, die regelmaessig + # attraktive Begegnungen mit den hellen Planeten liefern. + star_defs = [ + ("Alrescha", 2.0341, 2.7638), + ("Hamal", 2.1196, 23.4624), + ("Menkar", 3.0380, 4.0897), + ("Aldebaran", 4.5987, 16.5093), + ("Elnath", 5.4382, 28.6075), + ("Pollux", 7.7553, 28.0262), + ("Alphard", 9.4598, -8.6586), + ("Regulus", 10.1395, 11.9672), + ("Denebola", 11.8177, 14.5721), + ("Spica", 13.4199, -11.1613), + ("Zubenelgenubi", 14.8479, -16.0418), + ("Antares", 16.4901, -26.4319), + ] + + approaches = [] + for planet_label, planet_body, planet_key in planet_defs: + for star_label, ra_hours, dec_deg in star_defs: + samples: list[tuple[datetime, float]] = [] + current = utc_start + while current <= utc_end: + samples.append((current, planet_fixed_equatorial_separation_deg(planet_body, ra_hours, dec_deg, observer, current))) + current += scan_step + if samples[-1][0] < utc_end: + samples.append((utc_end, planet_fixed_equatorial_separation_deg(planet_body, ra_hours, dec_deg, observer, utc_end))) + + seen_ranges: list[tuple[datetime, datetime]] = [] + for index in range(1, len(samples) - 1): + curr_t, curr_sep = samples[index] + prev_sep = samples[index - 1][1] + next_sep = samples[index + 1][1] + + if curr_sep > coarse_threshold: + continue + if curr_sep > prev_sep or curr_sep > next_sep: + continue + + left = max(utc_start, curr_t - scan_step) + right = min(utc_end, curr_t + scan_step) + + if any(not (right <= seen_left or left >= seen_right) for seen_left, seen_right in seen_ranges): + continue + + min_time_utc, min_sep = refine_planet_fixed_target_minimum_separation( + planet_body, + ra_hours, + dec_deg, + observer, + left, + right, + ) + local_dt = min_time_utc.astimezone(tz) + if local_dt.year != year or local_dt.month != month: + continue + if min_sep > max_sep_deg: + continue + + seen_ranges.append((left, right)) + approaches.append({ + "planet_key": planet_key, + "planet_label": planet_label, + "star_label": star_label, + "label": f"{planet_label} nahe {star_label}", + "separation_deg": float(min_sep), + "utc_iso": min_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"), + }) + + approaches.sort(key=lambda item: item["local_iso"]) + + deduplicated_approaches: list[dict] = [] + for approach in approaches: + if deduplicated_approaches: + previous = deduplicated_approaches[-1] + same_pair = ( + previous["planet_key"] == approach["planet_key"] + and previous["star_label"] == approach["star_label"] + ) + previous_local = datetime.fromisoformat(previous["local_iso"]) + current_local = datetime.fromisoformat(approach["local_iso"]) + if same_pair and abs((current_local - previous_local).total_seconds()) <= 36 * 3600: + if float(approach["separation_deg"]) < float(previous["separation_deg"]): + deduplicated_approaches[-1] = approach + continue + deduplicated_approaches.append(approach) + + return { + "ok": True, + "action": "planet_bright_star_approaches_for_month", + "selected": { + "year": year, + "month": month, + "timezone": timezone_name, + "max_separation_deg": max_sep_deg, + }, + "approaches": deduplicated_approaches, + } + + def action_moon_deep_sky_approaches_for_month(args: list[str]) -> dict: if len(args) != 7: fail( @@ -3430,7 +3619,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", "golden_handle_for_month", "season_changes_for_month", "time_changes_for_month", "moon_planet_approaches", "moon_deep_sky_approaches_for_month", "planet_conjunctions_for_month", "eclipses_for_month", "moon_apsides_for_month", "sun_apsides_for_month", "inner_planet_elongations_for_month", "venus_peak_magnitude_for_month", "outer_planet_events_for_month", "outer_planet_stations_for_month", "jupiter_moons_one_side_for_month", "mercury_good_visibility_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", "golden_handle_for_month", "season_changes_for_month", "time_changes_for_month", "moon_planet_approaches", "moon_deep_sky_approaches_for_month", "planet_bright_star_approaches_for_month", "planet_conjunctions_for_month", "eclipses_for_month", "moon_apsides_for_month", "sun_apsides_for_month", "inner_planet_elongations_for_month", "venus_peak_magnitude_for_month", "outer_planet_events_for_month", "outer_planet_stations_for_month", "jupiter_moons_one_side_for_month", "mercury_good_visibility_for_month", "moon_star_occultations_for_month"]}) action = sys.argv[1] args = sys.argv[2:] @@ -3495,6 +3684,11 @@ def main() -> None: print(json.dumps(result, ensure_ascii=False)) return + if action == "planet_bright_star_approaches_for_month": + result = action_planet_bright_star_approaches_for_month(args) + print(json.dumps(result, ensure_ascii=False)) + return + if action == "planet_conjunctions_for_month": result = action_planet_conjunctions_for_month(args) print(json.dumps(result, ensure_ascii=False)) @@ -3550,7 +3744,7 @@ def main() -> None: 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", "golden_handle_for_month", "season_changes_for_month", "time_changes_for_month", "moon_planet_approaches", "moon_deep_sky_approaches_for_month", "planet_conjunctions_for_month", "eclipses_for_month", "moon_apsides_for_month", "sun_apsides_for_month", "inner_planet_elongations_for_month", "venus_peak_magnitude_for_month", "outer_planet_events_for_month", "outer_planet_stations_for_month", "jupiter_moons_one_side_for_month", "mercury_good_visibility_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", "golden_handle_for_month", "season_changes_for_month", "time_changes_for_month", "moon_planet_approaches", "moon_deep_sky_approaches_for_month", "planet_bright_star_approaches_for_month", "planet_conjunctions_for_month", "eclipses_for_month", "moon_apsides_for_month", "sun_apsides_for_month", "inner_planet_elongations_for_month", "venus_peak_magnitude_for_month", "outer_planet_events_for_month", "outer_planet_stations_for_month", "jupiter_moons_one_side_for_month", "mercury_good_visibility_for_month", "moon_star_occultations_for_month"]}) if __name__ == "__main__":