diff --git a/BERECHNUNGSGRUNDLAGEN.md b/BERECHNUNGSGRUNDLAGEN.md
index f9e11de..2fc1279 100644
--- a/BERECHNUNGSGRUNDLAGEN.md
+++ b/BERECHNUNGSGRUNDLAGEN.md
@@ -49,6 +49,7 @@ Nicht jede Berechnung ist Skyfield oder Astronomy Engine:
| `sonnenfinsternis.php` | **Astronomy Engine**, einschließlich lokaler Berechnung für den Standardstandort. |
| `mondfinsternis.php` | **Astronomy Engine**. Der Zeitpunkt der Finsternis ist global; eine lokale Sichtbarkeitsbewertung ist davon getrennt. |
| `sternbedeckungen.php` | **Astronomy Engine** über die lokale Python-API. Topozentrische Koordinaten und der Standardstandort werden berücksichtigt. |
+| `mondplanetenbedeckungen.php` | **Astronomy Engine** über die lokale Python-API. Durchsucht ein ganzes Jahr nach Mond-Planeten-Bedeckungen (Merkur bis Neptun) für den Standardstandort; berücksichtigt die scheinbare Scheibengröße von Mond und Planet. |
| `moonphase.php` | **Astronomy Engine** im Browser beziehungsweise über die lokale Python-API. |
| `moonyear.php` | **Astronomy Engine** für Mondphasen und Mondpositionen. |
| `satellitenhimmel.php` | **SGP4** für Satellitenbahnen; Astronomy Engine für astronomischen Kontext wie Sonne, Mond und Dämmerung. |
diff --git a/public/auth_helpers.php b/public/auth_helpers.php
index e20df23..011edd2 100644
--- a/public/auth_helpers.php
+++ b/public/auth_helpers.php
@@ -134,6 +134,7 @@ function manageablePublicPages(): array
'sonnenfinsternis.php' => 'Sonnenfinsternis',
'bedeckung.php' => 'Bedeckungsphänomene',
'sternbedeckungen.php' => 'Mond-Stern-Bedeckungen',
+ 'mondplanetenbedeckungen.php' => 'Mond-Planeten-Bedeckungen',
'monatsvorhersage.php' => 'Monatsvorhersage',
'moonyear.php' => 'Mondphasen-Jahresübersicht',
'wochenvorhersage.php' => 'Wochenvorhersage',
diff --git a/public/css/style.css b/public/css/style.css
index c771b43..ff7a58a 100644
--- a/public/css/style.css
+++ b/public/css/style.css
@@ -4676,6 +4676,12 @@ body.solarsystem-page:not(.jupitersystem-page) .solarsystem-stage-wrap {
box-shadow: inset 0 0 0 1px rgba(143, 230, 141, 0.22);
}
+.occult-visibility-below {
+ background: rgba(136, 148, 173, 0.16);
+ color: #9aa6c2;
+ box-shadow: inset 0 0 0 1px rgba(154, 166, 194, 0.22);
+}
+
.occult-detail-card {
overflow: hidden;
}
diff --git a/public/header.php b/public/header.php
index 30d4528..020555b 100644
--- a/public/header.php
+++ b/public/header.php
@@ -146,6 +146,7 @@ $menuGroups = [
['href' => 'sonnenfinsternis.php', 'label' => 'Sonnenfinsternis'],
['href' => 'bedeckung.php', 'label' => 'Bedeckungsphänomene'],
['href' => 'sternbedeckungen.php', 'label' => 'Mond-Stern-Bedeckungen'],
+ ['href' => 'mondplanetenbedeckungen.php', 'label' => 'Mond-Planeten-Bedeckungen'],
],
],
[
diff --git a/public/mondplanetenbedeckungen.php b/public/mondplanetenbedeckungen.php
new file mode 100644
index 0000000..d259273
--- /dev/null
+++ b/public/mondplanetenbedeckungen.php
@@ -0,0 +1,1284 @@
+&1';
+ $output = [];
+ $resultCode = 0;
+ exec($command, $output, $resultCode);
+
+ $joined = trim(implode("\n", $output));
+ $debugOutput = $output;
+
+ if ($resultCode !== 0) {
+ $lastError = $joined !== '' ? $joined : ('Fehlercode ' . $resultCode . ' bei ' . $pythonBinary);
+ continue;
+ }
+
+ $decoded = json_decode($joined, true);
+ if (!is_array($decoded)) {
+ $lastError = $joined !== '' ? 'Python-Ausgabe war kein gültiges JSON.' : 'Python lieferte keine Ausgabe zurück.';
+ continue;
+ }
+
+ if (!($decoded['ok'] ?? false)) {
+ return [
+ 'ok' => false,
+ 'error' => (string)($decoded['error'] ?? 'Unbekannter Python-Fehler'),
+ 'debug' => $joined,
+ ];
+ }
+
+ return ['ok' => true, 'data' => $decoded];
+ }
+
+ return [
+ 'ok' => false,
+ 'error' => $lastError,
+ 'debug' => implode("\n", $debugOutput),
+ ];
+}
+
+$defaultLocation = null;
+$pdo = null;
+$pageError = null;
+$pageLocation = null;
+$detailMapStars = [];
+$constellationLines = [];
+$detailMapCatalogLimit = 8.0;
+$isAjax = isset($_POST['ajax']) && (string)$_POST['ajax'] === '1';
+
+try {
+ $config = require __DIR__ . '/../config/database.php';
+ $dsn = sprintf(
+ 'mysql:host=%s;dbname=%s;charset=%s',
+ $config['host'],
+ $config['dbname'],
+ $config['charset'] ?? 'utf8mb4'
+ );
+
+ $pdo = new PDO(
+ $dsn,
+ $config['user'],
+ $config['pass'],
+ [
+ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
+ PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
+ ]
+ );
+
+ if ($loggedIn && isset($_SESSION['user_id'])) {
+ $stmtLocation = $pdo->prepare(
+ 'SELECT id, name, latitude, longitude, elevation, timezone
+ FROM app_user_locations
+ WHERE user_id = ? AND is_default = 1
+ LIMIT 1'
+ );
+ $stmtLocation->execute([(int)$_SESSION['user_id']]);
+ $defaultLocation = $stmtLocation->fetch() ?: null;
+ }
+} catch (Throwable $exception) {
+ $pageError = $exception->getMessage();
+}
+
+if (!empty($defaultLocation) && is_array($defaultLocation)) {
+ $pageLocation = [
+ 'name' => (string)($defaultLocation['name'] ?? ''),
+ 'latitude' => isset($defaultLocation['latitude']) ? (float)$defaultLocation['latitude'] : null,
+ 'longitude' => isset($defaultLocation['longitude']) ? (float)$defaultLocation['longitude'] : null,
+ 'elevation' => isset($defaultLocation['elevation']) && $defaultLocation['elevation'] !== null ? (float)$defaultLocation['elevation'] : 0.0,
+ 'timezone' => (string)($defaultLocation['timezone'] ?? 'Europe/Berlin'),
+ ];
+}
+
+$timezoneName = $pageLocation['timezone'] ?? 'Europe/Berlin';
+try {
+ $timezone = new DateTimeZone($timezoneName);
+} catch (Throwable $exception) {
+ $timezoneName = 'Europe/Berlin';
+ $timezone = new DateTimeZone($timezoneName);
+}
+
+$todayYear = (int)(new DateTimeImmutable('now', $timezone))->format('Y');
+$selectedYear = isset($_POST['year']) && preg_match('/^\d{4}$/', (string)$_POST['year'])
+ ? (int)$_POST['year']
+ : $todayYear;
+$selectedYear = max(1900, min(2100, $selectedYear));
+
+if (!$isAjax && $pageError === null && $pdo instanceof PDO && $pageLocation !== null && $pageLocation['latitude'] !== null && $pageLocation['longitude'] !== null) {
+ try {
+ $stmtDetailMap = $pdo->query(
+ "SELECT `hip`, `proper`, `bf`, `con`, `mag`, `ra`, `dec`, `dist`
+ FROM `star_hipparcos`
+ WHERE `mag` IS NOT NULL
+ AND `ra` IS NOT NULL
+ AND `dec` IS NOT NULL
+ AND `mag` <= 8.0
+ ORDER BY `mag` ASC, `proper` ASC, `bf` ASC, `hip` ASC"
+ );
+
+ foreach ($stmtDetailMap->fetchAll() as $row) {
+ $proper = trim((string)($row['proper'] ?? ''));
+ $bf = trim((string)($row['bf'] ?? ''));
+ $hip = isset($row['hip']) ? (int)$row['hip'] : 0;
+ $label = $proper !== '' ? $proper : ($bf !== '' ? $bf : ($hip > 0 ? 'HIP ' . $hip : 'Unbenannter Stern'));
+
+ $detailMapStars[] = [
+ 'hip' => $hip,
+ 'label' => $label,
+ 'constellation' => trim((string)($row['con'] ?? '')),
+ 'mag' => isset($row['mag']) ? (float)$row['mag'] : null,
+ 'ra' => isset($row['ra']) ? (float)$row['ra'] : null,
+ 'dec' => isset($row['dec']) ? (float)$row['dec'] : null,
+ 'distLy' => isset($row['dist']) && $row['dist'] !== null && (float)$row['dist'] > 0.0
+ ? (float)$row['dist'] * 3.26156
+ : 1000.0,
+ ];
+ }
+
+ $stmtLines = $pdo->query(
+ "SELECT
+ cl.`id` AS line_id,
+ clp.`point_index`,
+ sh.`ra` AS ra_hours,
+ sh.`dec` AS dec_deg
+ FROM `constellation_lines` cl
+ INNER JOIN `constellation_line_points` clp
+ ON clp.`line_id` = cl.`id`
+ INNER JOIN `star_hipparcos` sh
+ ON sh.`hip` = clp.`hip_number`
+ WHERE sh.`ra` IS NOT NULL
+ AND sh.`dec` IS NOT NULL
+ ORDER BY cl.`id`, clp.`point_index`"
+ );
+
+ foreach ($stmtLines->fetchAll() as $row) {
+ $lineId = (int)$row['line_id'];
+ $constellationLines[$lineId][] = [
+ 'ra' => isset($row['ra_hours']) ? (float)$row['ra_hours'] : null,
+ 'dec' => isset($row['dec_deg']) ? (float)$row['dec_deg'] : null,
+ ];
+ }
+ } catch (Throwable $exception) {
+ $pageError = $exception->getMessage();
+ $detailMapStars = [];
+ $constellationLines = [];
+ }
+}
+
+if ($isAjax) {
+ header('Content-Type: application/json; charset=utf-8');
+
+ if ($pageLocation === null || $pageLocation['latitude'] === null || $pageLocation['longitude'] === null) {
+ echo json_encode([
+ 'ok' => false,
+ 'error' => 'Kein Standard-Standort gesetzt. Bitte zuerst in den Einstellungen einen Standard-Standort hinterlegen.',
+ ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
+ exit;
+ }
+
+ if ($pageError !== null || !($pdo instanceof PDO)) {
+ echo json_encode([
+ 'ok' => false,
+ 'error' => $pageError ?? 'Die Datenbank ist nicht verfuegbar.',
+ ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
+ exit;
+ }
+
+ try {
+ $pythonResult = runPythonApi(
+ __DIR__ . '/py/api.py',
+ 'moon_planet_occultations_for_year',
+ [
+ (string)$pageLocation['latitude'],
+ (string)$pageLocation['longitude'],
+ (string)$pageLocation['elevation'],
+ (string)$selectedYear,
+ $timezoneName,
+ ]
+ );
+
+ if (!$pythonResult['ok']) {
+ echo json_encode([
+ 'ok' => false,
+ 'error' => $pythonResult['error'] ?? 'Unbekannter Python-Fehler',
+ 'debug' => $pythonResult['debug'] ?? null,
+ ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
+ exit;
+ }
+
+ echo json_encode([
+ 'ok' => true,
+ 'results' => $pythonResult['data']['results'] ?? [],
+ 'selected_year' => $selectedYear,
+ ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
+ exit;
+ } catch (Throwable $exception) {
+ echo json_encode([
+ 'ok' => false,
+ 'error' => $exception->getMessage(),
+ ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
+ exit;
+ }
+}
+
+require __DIR__ . '/header.php';
+
+if (!empty($currentLocation) && is_array($currentLocation)) {
+ $pageLocation = [
+ 'name' => (string)($currentLocation['name'] ?? ''),
+ 'latitude' => isset($currentLocation['latitude']) ? (float)$currentLocation['latitude'] : null,
+ 'longitude' => isset($currentLocation['longitude']) ? (float)$currentLocation['longitude'] : null,
+ 'elevation' => isset($currentLocation['elevation']) && $currentLocation['elevation'] !== null ? (float)$currentLocation['elevation'] : 0.0,
+ 'timezone' => (string)($currentLocation['timezone'] ?? 'Europe/Berlin'),
+ ];
+}
+?>
+
+
+
+
+
+
+
+
Berechnung laeuft
+
+
+ Berechnung ...
+
+
+
+ Fortschritt
+ 0 %
+
+
+
+
+
+
+
+
+
diff --git a/public/py/api.py b/public/py/api.py
index 5f93493..b55f458 100644
--- a/public/py/api.py
+++ b/public/py/api.py
@@ -5844,6 +5844,154 @@ def action_moon_star_occultations(args: list[str]) -> dict:
}
+PLANET_OCCULTATION_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"),
+ ("Uranus", astronomy.Body.Uranus, "uranus"),
+ ("Neptun", astronomy.Body.Neptune, "neptune"),
+]
+
+
+def compute_moon_planet_occultations_for_range(
+ observer: astronomy.Observer,
+ utc_start: datetime,
+ utc_end: datetime,
+ tz: ZoneInfo,
+) -> list[dict]:
+ scan_step = timedelta(hours=1)
+ coarse_threshold_deg = 3.0
+ results: list[dict] = []
+
+ for label, body, key in PLANET_OCCULTATION_DEFS:
+ samples: list[tuple[datetime, float]] = []
+ current = utc_start
+ while current <= utc_end:
+ samples.append((current, moon_planet_separation_deg(body, observer, current)))
+ current += scan_step
+ if samples[-1][0] < utc_end:
+ samples.append((utc_end, moon_planet_separation_deg(body, observer, utc_end)))
+
+ seen_ranges: list[tuple[datetime, datetime]] = []
+ for index in range(1, len(samples) - 1):
+ prev_t, prev_sep = samples[index - 1]
+ curr_t, curr_sep = samples[index]
+ next_t, next_sep = samples[index + 1]
+
+ if curr_sep > coarse_threshold_deg:
+ 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_minimum_separation(body, observer, left, right)
+ seen_ranges.append((left, right))
+
+ overlap_fraction = moon_planet_overlap_fraction(body, observer, min_time_utc)
+ if overlap_fraction <= 0.0:
+ continue
+
+ time_value = dt_to_time(min_time_utc)
+ moon_eq = astronomy.Equator(astronomy.Body.Moon, time_value, observer, True, True)
+ planet_eq = astronomy.Equator(body, time_value, observer, True, True)
+ sun_eq = astronomy.Equator(astronomy.Body.Sun, time_value, observer, True, True)
+ moon_hor = astronomy.Horizon(time_value, observer, moon_eq.ra, moon_eq.dec, astronomy.Refraction.Normal)
+ planet_hor = astronomy.Horizon(time_value, observer, planet_eq.ra, planet_eq.dec, astronomy.Refraction.Normal)
+ sun_hor = astronomy.Horizon(time_value, observer, sun_eq.ra, sun_eq.dec, astronomy.Refraction.Normal)
+
+ below_horizon = float(moon_hor.altitude) <= 0.0 or float(planet_hor.altitude) <= 0.0
+
+ ingress_time = refine_occultation_contact(body, observer, min_time_utc, -1)
+ egress_time = refine_occultation_contact(body, observer, min_time_utc, 1)
+
+ duration_seconds = None
+ if ingress_time is not None and egress_time is not None:
+ duration_seconds = int(round((egress_time - ingress_time).total_seconds()))
+
+ moon_radius_deg = moon_angular_radius_deg(float(moon_eq.dist))
+ planet_radius_deg = planet_angular_radius_deg(body, float(planet_eq.dist))
+
+ results.append({
+ "planet_key": key,
+ "planet_label": label,
+ "ingress": serialize_occultation_time(ingress_time, tz),
+ "maximum": serialize_occultation_time(min_time_utc, tz),
+ "egress": serialize_occultation_time(egress_time, tz),
+ "partial_start": ingress_time is None,
+ "partial_end": egress_time is None,
+ "duration_seconds": duration_seconds,
+ "separation_arcmin": float(min_sep) * 60.0,
+ "moon_radius_arcmin": moon_radius_deg * 60.0,
+ "planet_radius_arcsec": planet_radius_deg * 3600.0,
+ "overlap_fraction": float(overlap_fraction),
+ "is_total": overlap_fraction >= 0.999,
+ "moon_alt_deg": float(moon_hor.altitude),
+ "planet_alt_deg": float(planet_hor.altitude),
+ "sun_alt_deg": float(sun_hor.altitude),
+ "below_horizon": below_horizon,
+ "visibility": {"key": "below_horizon", "label": "Unter Horizont"} if below_horizon
+ else classify_visibility(float(sun_hor.altitude)),
+ })
+
+ results.sort(
+ key=lambda item: item["maximum"]["utc_iso"] if item["maximum"] else "9999-99-99T99:99:99Z"
+ )
+ return results
+
+
+def action_moon_planet_occultations_for_year(args: list[str]) -> dict:
+ if len(args) != 5:
+ fail(
+ "Aktion moon_planet_occultations_for_year erwartet 5 Argumente: latitude longitude elevation year timezone",
+ 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])
+ except ValueError as exc:
+ fail("Jahr ist ungueltig.", extra={"details": str(exc), "argv": args})
+
+ timezone_name = args[4]
+ 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, 1, 1, 0, 0, 0, tzinfo=tz)
+ local_end = datetime(year + 1, 1, 1, 0, 0, 0, tzinfo=tz)
+ utc_start = local_start.astimezone(timezone.utc)
+ utc_end = local_end.astimezone(timezone.utc)
+
+ results = compute_moon_planet_occultations_for_range(observer, utc_start, utc_end, tz)
+
+ return {
+ "ok": True,
+ "action": "moon_planet_occultations_for_year",
+ "selected": {
+ "year": year,
+ "timezone": timezone_name,
+ },
+ "observer": {
+ "latitude": latitude,
+ "longitude": longitude,
+ "elevation": elevation,
+ },
+ "results": results,
+ }
+
+
# ── satellite_passes ──────────────────────────────────────────────────────────
import math as _math
@@ -6415,7 +6563,7 @@ def action_barycenter(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", "solar_longitude_to_datetime", "current_solar_longitude", "astronomical_conversions", "comet_brightnesses", "favorite_comet_events_for_month", "moon_star_occultations", "moon_phase_details", "satellite_passes", "planet_rise_set", "planet_ephemeris", "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", "golden_gate_of_ecliptic_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", "planet_parades_for_month", "planet_constellation_changes_for_month", "sun_constellation_changes_for_year", "sun_constellation_offset_statistics", "moon_star_occultations_for_month"]})
+ fail("Es wurde keine Aktion uebergeben.", extra={"available_actions": ["sun_moon_rise_set", "solar_longitude_to_datetime", "current_solar_longitude", "astronomical_conversions", "comet_brightnesses", "favorite_comet_events_for_month", "moon_star_occultations", "moon_phase_details", "satellite_passes", "planet_rise_set", "planet_ephemeris", "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", "golden_gate_of_ecliptic_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", "planet_parades_for_month", "planet_constellation_changes_for_month", "sun_constellation_changes_for_year", "sun_constellation_offset_statistics", "moon_star_occultations_for_month", "moon_planet_occultations_for_year"]})
action = sys.argv[1]
args = sys.argv[2:]
@@ -6605,6 +6753,11 @@ def main() -> None:
print(json.dumps(result, ensure_ascii=True))
return
+ if action == "moon_planet_occultations_for_year":
+ result = action_moon_planet_occultations_for_year(args)
+ print(json.dumps(result, ensure_ascii=True))
+ return
+
if action == "lunar_eclipses_for_year":
result = action_lunar_eclipses_for_year(args)
print(json.dumps(result, ensure_ascii=True))
@@ -6615,7 +6768,7 @@ def main() -> None:
print(json.dumps(result, ensure_ascii=True))
return
- fail("Unbekannte Aktion.", extra={"action": action, "available_actions": ["sun_moon_rise_set", "solar_longitude_to_datetime", "current_solar_longitude", "astronomical_conversions", "twilight_chart", "comet_brightnesses", "favorite_comet_events_for_month", "moon_star_occultations", "moon_phase_details", "satellite_passes", "planet_rise_set", "planet_ephemeris", "planet_visibility_chart", "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", "golden_gate_of_ecliptic_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", "planet_parades_for_month", "planet_constellation_changes_for_month", "sun_constellation_changes_for_year", "sun_constellation_offset_statistics", "moon_star_occultations_for_month"]})
+ fail("Unbekannte Aktion.", extra={"action": action, "available_actions": ["sun_moon_rise_set", "solar_longitude_to_datetime", "current_solar_longitude", "astronomical_conversions", "twilight_chart", "comet_brightnesses", "favorite_comet_events_for_month", "moon_star_occultations", "moon_phase_details", "satellite_passes", "planet_rise_set", "planet_ephemeris", "planet_visibility_chart", "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", "golden_gate_of_ecliptic_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", "planet_parades_for_month", "planet_constellation_changes_for_month", "sun_constellation_changes_for_year", "sun_constellation_offset_statistics", "moon_star_occultations_for_month", "moon_planet_occultations_for_year"]})
if __name__ == "__main__":