Mondereignisse vollständig und kompakt ausgeben

This commit is contained in:
2026-09-12 11:44:49 +02:00
parent d62879cce3
commit 5bb05e69df
2 changed files with 101 additions and 17 deletions
+14 -8
View File
@@ -913,13 +913,16 @@ if (!function_exists('monthForecastBuildNarrativeText')) {
));
$phaseEvents = array_values(array_filter($sortedMoonEvents, static fn (array $e): bool => (string) ($e['type'] ?? '') === 'moon_phase'));
// Alle Mondphasen müssen erhalten bleiben. Die übrigen Mondereignisse
// werden weiterhin begrenzt, damit der Übersichtstext kompakt bleibt.
$nonPhaseEvents = array_slice(
array_values(array_filter($sortedMoonEvents, static fn (array $e): bool => (string) ($e['type'] ?? '') !== 'moon_phase')),
0,
10
);
// Alle Mondphasen und alle Mondereignisse werden vollständig übernommen.
$nonPhaseCandidates = array_values(array_filter(
$sortedMoonEvents,
static fn (array $e): bool => (string) ($e['type'] ?? '') !== 'moon_phase'
));
$nonPhaseEvents = $nonPhaseCandidates;
usort($nonPhaseEvents, static fn (array $a, array $b): int => strcmp(
(string) ($a['local_iso'] ?? $a['date'] ?? ''),
(string) ($b['local_iso'] ?? $b['date'] ?? '')
));
$moonSentences = [];
@@ -985,7 +988,7 @@ if (!function_exists('monthForecastBuildNarrativeText')) {
}
if ($goldenHandle !== null) {
$moonSentences[] = 'Am ' . $sd . ' zeigt der Mond den Goldenen Henkel: Ein Gebirgskamm am Mondrand leuchtet bereits im Sonnenlicht, während das Tal dahinter noch im Schatten liegt – ein lohnender Anblick schon im kleinen Teleskop.';
$moonSentences[] = 'Am ' . $sd . ' zeigt der Mond den Goldenen Henkel.';
}
if ($apsis !== null) {
@@ -1057,6 +1060,9 @@ if (!function_exists('monthForecastBuildNarrativeText')) {
if (str_starts_with($apLabel, 'Mond nahe ')) {
$target = substr($apLabel, strlen('Mond nahe '));
$moonSentences[] = 'Am ' . $apSd . ' zieht der Mond' . $sepStr . ' an ' . $target . ' vorbei.';
} elseif (str_starts_with($apLabel, 'Mond streift ')) {
$target = substr($apLabel, strlen('Mond streift '));
$moonSentences[] = 'Am ' . $apSd . ' streift der Mond ' . $target . '.';
} elseif (str_starts_with($apLabel, 'Mond bedeckt ')) {
$target = substr($apLabel, strlen('Mond bedeckt '));
$moonSentences[] = 'Am ' . $apSd . ' bedeckt der Mond ' . $target . '.';
+87 -9
View File
@@ -2917,15 +2917,28 @@ def moon_planet_occultation_margin_deg(
moon_eq = astronomy.Equator(astronomy.Body.Moon, time_value, observer, True, True)
body_eq = astronomy.Equator(body, time_value, observer, True, True)
moon_radius = moon_angular_radius_deg(float(moon_eq.dist))
planet_radius_km = PLANET_RADIUS_KM[body]
planet_ratio = planet_radius_km / (float(body_eq.dist) * astronomy.KM_PER_AU)
planet_radius = math.degrees(math.asin(max(-1.0, min(1.0, planet_ratio))))
planet_radius = planet_angular_radius_deg(body, float(body_eq.dist))
separation = spherical_separation_deg(
float(moon_eq.ra), float(moon_eq.dec), float(body_eq.ra), float(body_eq.dec)
)
return separation - moon_radius - planet_radius
def moon_planet_overlap_fraction(
body: astronomy.Body,
observer: astronomy.Observer,
dt_utc: datetime,
) -> float:
time_value = dt_to_time(dt_utc)
moon_eq = astronomy.Equator(astronomy.Body.Moon, time_value, observer, True, True)
body_eq = astronomy.Equator(body, time_value, observer, True, True)
return circle_overlap_fraction(
moon_angular_radius_deg(float(moon_eq.dist)),
planet_angular_radius_deg(body, float(body_eq.dist)),
moon_planet_separation_deg(body, observer, dt_utc),
)
def refine_occultation_contact(
body: astronomy.Body,
observer: astronomy.Observer,
@@ -3383,8 +3396,8 @@ def action_moon_planet_approaches(args: list[str]) -> dict:
continue
seen_ranges.append((left, right))
occultation_margin = moon_planet_occultation_margin_deg(body, observer, min_time_utc)
if occultation_margin <= 0.0:
overlap_fraction = moon_planet_overlap_fraction(body, observer, min_time_utc)
if overlap_fraction >= (1.0 / 3.0):
contact_times = [
("start", refine_occultation_contact(body, observer, min_time_utc, -1), "Beginn der Bedeckung"),
("maximum", min_time_utc, "Größte Bedeckung"),
@@ -3407,6 +3420,19 @@ def action_moon_planet_approaches(args: list[str]) -> dict:
})
continue
if overlap_fraction > 0.0:
approaches.append({
"planet_key": key,
"planet_label": label,
"label": f"Mond streift {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"),
})
continue
approaches.append({
"planet_key": key,
"planet_label": label,
@@ -3625,12 +3651,13 @@ def action_moon_deep_sky_approaches_for_month(args: list[str]) -> dict:
coarse_threshold = max_sep_deg + 1.0
target_defs = [
("pleiades", "Plejaden", 3.7833, 24.1167),
("praesepe", "Praesepe", 8.6667, 19.9833),
# Katalogisierte major_axis-Werte aus dso_objects, in Grad.
("pleiades", "Plejaden", 3.7833, 24.1167, 60.0 / 60.0),
("praesepe", "Praesepe", 8.6667, 19.9833, 108.6 / 60.0),
]
approaches = []
for key, label, ra_hours, dec_deg in target_defs:
for key, label, ra_hours, dec_deg, diameter_deg in target_defs:
samples: list[tuple[datetime, float]] = []
current = utc_start
while current <= utc_end:
@@ -3664,10 +3691,22 @@ def action_moon_deep_sky_approaches_for_month(args: list[str]) -> dict:
continue
seen_ranges.append((left, right))
moon_eq = astronomy.Equator(astronomy.Body.Moon, dt_to_time(min_time_utc), observer, True, True)
overlap_fraction = moon_disk_overlap_fraction(
diameter_deg / 2.0,
moon_angular_radius_deg(float(moon_eq.dist)),
min_sep,
)
if overlap_fraction >= (1.0 / 3.0):
event_label = f"Mond bedeckt {label}"
elif overlap_fraction > 0.0:
event_label = f"Mond streift {label}"
else:
event_label = f"Mond nahe {label}"
approaches.append({
"target_key": key,
"target_label": label,
"label": f"Mond nahe {label}",
"label": event_label,
"separation_deg": float(min_sep),
"utc_iso": min_time_utc.isoformat().replace("+00:00", "Z"),
"local_iso": local_dt.isoformat(),
@@ -5133,6 +5172,45 @@ def moon_angular_radius_deg(distance_au: float) -> float:
return math.degrees(math.asin(ratio))
def planet_angular_radius_deg(body: astronomy.Body, distance_au: float) -> float:
if not math.isfinite(distance_au) or distance_au <= 0:
return 0.0
ratio = PLANET_RADIUS_KM[body] / (distance_au * astronomy.KM_PER_AU)
ratio = max(-1.0, min(1.0, ratio))
return math.degrees(math.asin(ratio))
def circle_overlap_fraction(moon_radius_deg: float, target_radius_deg: float, separation_deg: float) -> float:
"""Berechnet den Anteil der Zielscheibe, den der Mond überdeckt."""
if moon_radius_deg <= 0.0 or target_radius_deg <= 0.0:
return 0.0
if separation_deg >= moon_radius_deg + target_radius_deg:
return 0.0
if separation_deg <= abs(moon_radius_deg - target_radius_deg):
overlap_area = math.pi * min(moon_radius_deg, target_radius_deg) ** 2
else:
moon_r2 = moon_radius_deg ** 2
target_r2 = target_radius_deg ** 2
moon_angle = math.acos((separation_deg ** 2 + moon_r2 - target_r2) / (2.0 * separation_deg * moon_radius_deg))
target_angle = math.acos((separation_deg ** 2 + target_r2 - moon_r2) / (2.0 * separation_deg * target_radius_deg))
triangle = 0.5 * math.sqrt(max(0.0, (
-separation_deg + moon_radius_deg + target_radius_deg
) * (
separation_deg + moon_radius_deg - target_radius_deg
) * (
separation_deg - moon_radius_deg + target_radius_deg
) * (
separation_deg + moon_radius_deg + target_radius_deg
)))
overlap_area = moon_r2 * moon_angle + target_r2 * target_angle - triangle
return max(0.0, min(1.0, overlap_area / (math.pi * target_radius_deg ** 2)))
def moon_disk_overlap_fraction(target_radius_deg: float, moon_radius_deg: float, separation_deg: float) -> float:
"""Berechnet bei Sternhaufen den überdeckten Anteil der Mondscheibe."""
return circle_overlap_fraction(target_radius_deg, moon_radius_deg, separation_deg)
def normalize_delta_ra_hours(delta_ra_hours: float) -> float:
while delta_ra_hours > 12.0:
delta_ra_hours -= 24.0