Mondbedeckungen von Planeten als Kontakte ausgeben
This commit is contained in:
@@ -1022,6 +1022,19 @@ if (!function_exists('monthForecastBuildNarrativeText')) {
|
|||||||
$sepStr = ' in nur ' . $sepMatch[1] . ' Abstand';
|
$sepStr = ' in nur ' . $sepMatch[1] . ' Abstand';
|
||||||
}
|
}
|
||||||
$apSd = $shortDate($apDate !== '' ? $apDate : $date);
|
$apSd = $shortDate($apDate !== '' ? $apDate : $date);
|
||||||
|
$eventKind = (string) ($ap['event_kind'] ?? '');
|
||||||
|
if ($eventKind !== '' && str_starts_with($apLabel, 'Mond bedeckt ')) {
|
||||||
|
$target = substr($apLabel, strlen('Mond bedeckt '));
|
||||||
|
$apTime = trim((string) ($ap['time'] ?? ''));
|
||||||
|
$timeClause = $apTime !== '' ? ' um ' . $apTime . ' Uhr' : '';
|
||||||
|
$moonSentences[] = match ($eventKind) {
|
||||||
|
'start' => 'Am ' . $apSd . $timeClause . ' beginnt die Bedeckung von ' . $target . ' durch den Mond.',
|
||||||
|
'maximum' => 'Am ' . $apSd . $timeClause . ' erreicht die Bedeckung von ' . $target . ' durch den Mond ihr Maximum.',
|
||||||
|
'end' => 'Am ' . $apSd . $timeClause . ' endet die Bedeckung von ' . $target . ' durch den Mond.',
|
||||||
|
default => 'Am ' . $apSd . $timeClause . ' bedeckt der Mond ' . $target . '.',
|
||||||
|
};
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (str_starts_with($apLabel, 'Mond nahe ')) {
|
if (str_starts_with($apLabel, 'Mond nahe ')) {
|
||||||
$target = substr($apLabel, strlen('Mond nahe '));
|
$target = substr($apLabel, strlen('Mond nahe '));
|
||||||
$moonSentences[] = 'Am ' . $apSd . ' zieht der Mond' . $sepStr . ' an ' . $target . ' vorbei.';
|
$moonSentences[] = 'Am ' . $apSd . ' zieht der Mond' . $sepStr . ' an ' . $target . ' vorbei.';
|
||||||
@@ -2451,7 +2464,8 @@ foreach ($moonPlanetApproaches as $approachEvent) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$distanceText = isset($approachEvent['separation_deg'])
|
$eventKind = (string) ($approachEvent['event_kind'] ?? '');
|
||||||
|
$distanceText = $eventKind === '' && isset($approachEvent['separation_deg'])
|
||||||
? number_format((float) $approachEvent['separation_deg'], 2, ',', '') . ' Grad'
|
? number_format((float) $approachEvent['separation_deg'], 2, ',', '') . ' Grad'
|
||||||
: '';
|
: '';
|
||||||
|
|
||||||
@@ -2460,7 +2474,7 @@ foreach ($moonPlanetApproaches as $approachEvent) {
|
|||||||
'date' => (string) ($approachEvent['local_date'] ?? ''),
|
'date' => (string) ($approachEvent['local_date'] ?? ''),
|
||||||
'time' => (string) ($approachEvent['local_time'] ?? ''),
|
'time' => (string) ($approachEvent['local_time'] ?? ''),
|
||||||
'local_iso' => (string) ($approachEvent['local_iso'] ?? ''),
|
'local_iso' => (string) ($approachEvent['local_iso'] ?? ''),
|
||||||
'type' => 'moon_planet',
|
'type' => $eventKind !== '' ? 'moon_occultation' : 'moon_planet',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+121
-1
@@ -18,6 +18,15 @@ from skyfield import almanac
|
|||||||
|
|
||||||
MOON_RADIUS_KM = 1737.4
|
MOON_RADIUS_KM = 1737.4
|
||||||
SYNODIC_MONTH = 29.530588853
|
SYNODIC_MONTH = 29.530588853
|
||||||
|
PLANET_RADIUS_KM = {
|
||||||
|
astronomy.Body.Mercury: 2439.7,
|
||||||
|
astronomy.Body.Venus: 6051.8,
|
||||||
|
astronomy.Body.Mars: 3396.2,
|
||||||
|
astronomy.Body.Jupiter: astronomy.JUPITER_EQUATORIAL_RADIUS_KM,
|
||||||
|
astronomy.Body.Saturn: 60268.0,
|
||||||
|
astronomy.Body.Uranus: 25559.0,
|
||||||
|
astronomy.Body.Neptune: 24764.0,
|
||||||
|
}
|
||||||
STAR_BODIES = [
|
STAR_BODIES = [
|
||||||
astronomy.Body.Star1,
|
astronomy.Body.Star1,
|
||||||
astronomy.Body.Star2,
|
astronomy.Body.Star2,
|
||||||
@@ -67,6 +76,30 @@ def dt_to_time(dt_utc: datetime) -> astronomy.Time:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def jupiter_emission_datetime(observation_dt: datetime) -> datetime:
|
||||||
|
"""Berechnet den Emissionszeitpunkt für eine Beobachtung von der Erde."""
|
||||||
|
observation_dt = observation_dt.astimezone(timezone.utc)
|
||||||
|
emission_dt = observation_dt
|
||||||
|
|
||||||
|
# Die Jupiterentfernung wird am zunächst geschätzten Emissionszeitpunkt
|
||||||
|
# neu bestimmt. Zwei Durchläufe reichen für die Lichtlaufzeit im
|
||||||
|
# Jupiter-System deutlich aus.
|
||||||
|
for _ in range(2):
|
||||||
|
geo = astronomy.GeoVector(
|
||||||
|
astronomy.Body.Jupiter,
|
||||||
|
dt_to_time(emission_dt),
|
||||||
|
True,
|
||||||
|
)
|
||||||
|
distance_au = math.sqrt(
|
||||||
|
(float(geo.x) * float(geo.x))
|
||||||
|
+ (float(geo.y) * float(geo.y))
|
||||||
|
+ (float(geo.z) * float(geo.z))
|
||||||
|
)
|
||||||
|
emission_dt = observation_dt - timedelta(days=distance_au / astronomy.C_AUDAY)
|
||||||
|
|
||||||
|
return emission_dt
|
||||||
|
|
||||||
|
|
||||||
def time_to_datetime(time_value: astronomy.Time) -> datetime:
|
def time_to_datetime(time_value: astronomy.Time) -> datetime:
|
||||||
year, month, day, hour, minute, second = time_value.Calendar()
|
year, month, day, hour, minute, second = time_value.Calendar()
|
||||||
second_int = int(second)
|
second_int = int(second)
|
||||||
@@ -2874,6 +2907,65 @@ def moon_planet_separation_deg(
|
|||||||
return spherical_separation_deg(float(moon_eq.ra), float(moon_eq.dec), float(body_eq.ra), float(body_eq.dec))
|
return spherical_separation_deg(float(moon_eq.ra), float(moon_eq.dec), float(body_eq.ra), float(body_eq.dec))
|
||||||
|
|
||||||
|
|
||||||
|
def moon_planet_occultation_margin_deg(
|
||||||
|
body: astronomy.Body,
|
||||||
|
observer: astronomy.Observer,
|
||||||
|
dt_utc: datetime,
|
||||||
|
) -> float:
|
||||||
|
"""Negativ bedeutet Überlappung der scheinbaren Mond- und Planetenscheiben."""
|
||||||
|
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)
|
||||||
|
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))))
|
||||||
|
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 refine_occultation_contact(
|
||||||
|
body: astronomy.Body,
|
||||||
|
observer: astronomy.Observer,
|
||||||
|
center_utc: datetime,
|
||||||
|
direction: int,
|
||||||
|
) -> datetime | None:
|
||||||
|
"""Findet den Scheibenkontakt vor oder nach der größten Bedeckung."""
|
||||||
|
step = timedelta(minutes=5)
|
||||||
|
inner = center_utc
|
||||||
|
inner_margin = moon_planet_occultation_margin_deg(body, observer, inner)
|
||||||
|
if inner_margin > 0.0:
|
||||||
|
return None
|
||||||
|
|
||||||
|
outer = inner
|
||||||
|
for _ in range(288): # maximal 24 Stunden vom Maximum entfernt suchen
|
||||||
|
outer = outer + (step if direction > 0 else -step)
|
||||||
|
outer_margin = moon_planet_occultation_margin_deg(body, observer, outer)
|
||||||
|
if outer_margin >= 0.0:
|
||||||
|
left, right = (inner, outer) if direction > 0 else (outer, inner)
|
||||||
|
for _ in range(40):
|
||||||
|
middle = left + (right - left) / 2
|
||||||
|
middle_margin = moon_planet_occultation_margin_deg(body, observer, middle)
|
||||||
|
if direction > 0:
|
||||||
|
if middle_margin < 0.0:
|
||||||
|
left = middle
|
||||||
|
else:
|
||||||
|
right = middle
|
||||||
|
else:
|
||||||
|
if middle_margin < 0.0:
|
||||||
|
right = middle
|
||||||
|
else:
|
||||||
|
left = middle
|
||||||
|
return right if direction > 0 else left
|
||||||
|
|
||||||
|
inner = outer
|
||||||
|
inner_margin = outer_margin
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def moon_fixed_equatorial_separation_deg(
|
def moon_fixed_equatorial_separation_deg(
|
||||||
ra_hours: float,
|
ra_hours: float,
|
||||||
dec_deg: float,
|
dec_deg: float,
|
||||||
@@ -3291,6 +3383,30 @@ def action_moon_planet_approaches(args: list[str]) -> dict:
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
seen_ranges.append((left, right))
|
seen_ranges.append((left, right))
|
||||||
|
occultation_margin = moon_planet_occultation_margin_deg(body, observer, min_time_utc)
|
||||||
|
if occultation_margin <= 0.0:
|
||||||
|
contact_times = [
|
||||||
|
("start", refine_occultation_contact(body, observer, min_time_utc, -1), "Beginn der Bedeckung"),
|
||||||
|
("maximum", min_time_utc, "Größte Bedeckung"),
|
||||||
|
("end", refine_occultation_contact(body, observer, min_time_utc, 1), "Ende der Bedeckung"),
|
||||||
|
]
|
||||||
|
if all(contact_time is not None for _, contact_time, _ in contact_times):
|
||||||
|
for event_kind, contact_time, event_label in contact_times:
|
||||||
|
assert contact_time is not None
|
||||||
|
contact_local = contact_time.astimezone(tz)
|
||||||
|
approaches.append({
|
||||||
|
"planet_key": key,
|
||||||
|
"planet_label": label,
|
||||||
|
"label": f"Mond bedeckt {label} – {event_label}",
|
||||||
|
"event_kind": event_kind,
|
||||||
|
"separation_deg": float(min_sep),
|
||||||
|
"utc_iso": contact_time.isoformat().replace("+00:00", "Z"),
|
||||||
|
"local_iso": contact_local.isoformat(),
|
||||||
|
"local_date": contact_local.strftime("%d.%m.%Y"),
|
||||||
|
"local_time": contact_local.strftime("%H:%M"),
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
|
||||||
approaches.append({
|
approaches.append({
|
||||||
"planet_key": key,
|
"planet_key": key,
|
||||||
"planet_label": label,
|
"planet_label": label,
|
||||||
@@ -4644,7 +4760,11 @@ def action_jupiter_moons_one_side_for_month(args: list[str]) -> dict:
|
|||||||
moon_keys = ["io", "europa", "ganymede", "callisto"]
|
moon_keys = ["io", "europa", "ganymede", "callisto"]
|
||||||
|
|
||||||
def classify_side(dt_utc: datetime) -> tuple[str | None, str | None, dict[str, float], dict[str, float]]:
|
def classify_side(dt_utc: datetime) -> tuple[str | None, str | None, dict[str, float], dict[str, float]]:
|
||||||
time_value = dt_to_time(dt_utc)
|
# Die ausgegebenen Ereigniszeiten bleiben Beobachtungszeiten in UTC.
|
||||||
|
# Für die tatsächliche Stellung der Monde rechnen wir auf den
|
||||||
|
# Emissionszeitpunkt des Jupiter-Lichts zurück.
|
||||||
|
emission_dt = jupiter_emission_datetime(dt_utc)
|
||||||
|
time_value = dt_to_time(emission_dt)
|
||||||
rotation_eqj_to_ecl = astronomy.Rotation_EQJ_ECL()
|
rotation_eqj_to_ecl = astronomy.Rotation_EQJ_ECL()
|
||||||
jupiter_geo = astronomy.GeoVector(astronomy.Body.Jupiter, time_value, True)
|
jupiter_geo = astronomy.GeoVector(astronomy.Body.Jupiter, time_value, True)
|
||||||
jupiter_helio = astronomy.HelioVector(astronomy.Body.Jupiter, time_value)
|
jupiter_helio = astronomy.HelioVector(astronomy.Body.Jupiter, time_value)
|
||||||
|
|||||||
Reference in New Issue
Block a user