From d147c9cda843c1f663d0ac942c1a154b7b5bab38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Fri, 3 Apr 2026 20:10:29 +0200 Subject: [PATCH] Jupitermonde in MV eingabuet --- public/monatsvorhersage.php | 3536 +++++++++++++++++++-- public/monatsvorhersage_old.php | 38 + public/py/__pycache__/api.cpython-312.pyc | Bin 36561 -> 142605 bytes public/py/api.py | 329 +- 4 files changed, 3605 insertions(+), 298 deletions(-) diff --git a/public/monatsvorhersage.php b/public/monatsvorhersage.php index 622cf42..b82d2f6 100644 --- a/public/monatsvorhersage.php +++ b/public/monatsvorhersage.php @@ -11,12 +11,20 @@ $publicBasePath = './'; $currentYear = (int) date('Y'); $selectedYear = isset($_GET['year']) ? (int) $_GET['year'] : $currentYear; $selectedMonth = isset($_GET['month']) ? max(1, min(12, (int) $_GET['month'])) : (int) date('n'); +$starDatabaseMagnitudeLimit = 6.0; +$starDisplayMagnitudeLimit = 4.4; +$starLabelMagnitudeLimit = 2.0; +$messierDatabaseMagnitudeLimit = 4.0; +$messierDisplayMagnitudeLimit = 1.0; +$moonPlanetMaxSeparationDeg = 1.5; +$planetConjunctionMaxSeparationDeg = 1.0; +$moonOccultationMaxMag = 2.0; $years = range($currentYear - 2, $currentYear + 5); $months = [ 1 => 'Januar', 2 => 'Februar', - 3 => 'März', + 3 => 'Maerz', 4 => 'April', 5 => 'Mai', 6 => 'Juni', @@ -41,7 +49,314 @@ $monthSkyTimes = [ 11 => ['hour' => 22, 'minute' => 0], 12 => ['hour' => 22, 'minute' => 0], ]; -$selectedSkyTime = $monthSkyTimes[$selectedMonth] ?? ['hour' => 22, 'minute' => 0]; + +$starData = []; +$lineStarData = []; +$constellationLines = []; +$messierData = []; +$planetData = []; +$moonData = null; +$moonPhaseEvents = []; +$goldenHandleEvents = []; +$moonPlanetApproaches = []; +$planetConjunctions = []; +$eclipseEvents = []; +$moonApsisEvents = []; +$sunApsisEvents = []; +$innerPlanetElongationEvents = []; +$venusPeakMagnitudeEvents = []; +$outerPlanetEvents = []; +$outerPlanetStationEvents = []; +$moonOccultations = []; +$meteorShowerPeaks = []; +$seasonChanges = []; +$timeChanges = []; +$monthEventList = []; +$starDataError = null; + +if (!function_exists('buildMonthOccultationStarLabel')) { + function buildMonthOccultationStarLabel(array $row): string + { + $proper = trim((string) ($row['proper'] ?? '')); + if ($proper !== '') { + return $proper; + } + + $bayer = trim((string) ($row['bayer'] ?? '')); + $con = trim((string) ($row['con'] ?? '')); + if ($bayer !== '') { + return $con !== '' ? $bayer . ' ' . $con : $bayer; + } + + $hip = isset($row['hip']) ? (int) $row['hip'] : 0; + return $hip > 0 ? 'HIP ' . $hip : 'Unbenannter Stern'; + } +} + +if (!function_exists('normalizeMonthForecastDegree')) { + function normalizeMonthForecastDegree(float $value): float + { + $normalized = fmod($value, 360.0); + if ($normalized < 0.0) { + $normalized += 360.0; + } + + return $normalized; + } +} + +if (!function_exists('monthForecastSolarLongitude')) { + function monthForecastSolarLongitude(DateTimeImmutable $dateTime): float + { + $julianDay = ((float) $dateTime->format('U')) / 86400.0 + 2440587.5; + $daysSinceJ2000 = $julianDay - 2451545.0; + $meanLongitude = normalizeMonthForecastDegree(280.46 + 0.9856474 * $daysSinceJ2000); + $meanAnomalyRad = deg2rad(normalizeMonthForecastDegree(357.528 + 0.9856003 * $daysSinceJ2000)); + + return normalizeMonthForecastDegree( + $meanLongitude + + 1.915 * sin($meanAnomalyRad) + + 0.020 * sin(2.0 * $meanAnomalyRad) + ); + } +} + +if (!function_exists('monthForecastForwardDegreeDistance')) { + function monthForecastForwardDegreeDistance(float $from, float $to): float + { + return normalizeMonthForecastDegree($to - $from); + } +} + +if (!function_exists('monthForecastRadiantIsVisible')) { + function monthForecastRadiantIsVisible(float $latitudeDeg, ?float $declinationDeg): bool + { + if ($declinationDeg === null) { + return false; + } + + return abs($latitudeDeg - $declinationDeg) < 90.0; + } +} + +if (!function_exists('findMonthForecastSolarLongitudeCrossing')) { + function findMonthForecastSolarLongitudeCrossing( + DateTimeImmutable $rangeStartLocal, + DateTimeImmutable $rangeEndLocal, + float $targetLongitude, + DateTimeZone $displayTimezone + ): ?DateTimeImmutable { + $utc = new DateTimeZone('UTC'); + $rangeStartUtc = $rangeStartLocal->setTimezone($utc); + $rangeEndUtc = $rangeEndLocal->setTimezone($utc); + + $startLongitude = monthForecastSolarLongitude($rangeStartUtc); + $endLongitude = monthForecastSolarLongitude($rangeEndUtc); + $targetDelta = monthForecastForwardDegreeDistance($startLongitude, $targetLongitude); + $rangeDelta = monthForecastForwardDegreeDistance($startLongitude, $endLongitude); + + if ($targetDelta > $rangeDelta + 0.0001) { + return null; + } + + $low = (int) $rangeStartUtc->format('U'); + $high = (int) $rangeEndUtc->format('U'); + + for ($i = 0; $i < 40; $i++) { + $mid = (int) floor(($low + $high) / 2); + $midLongitude = monthForecastSolarLongitude(new DateTimeImmutable('@' . $mid)); + $midDelta = monthForecastForwardDegreeDistance($startLongitude, $midLongitude); + + if ($midDelta < $targetDelta) { + $low = $mid; + } else { + $high = $mid; + } + } + + return (new DateTimeImmutable('@' . $high))->setTimezone($displayTimezone); + } +} + +try { + $dbConfig = require __DIR__ . '/../config/database.php'; + $dsn = sprintf( + 'mysql:host=%s;dbname=%s;charset=%s', + $dbConfig['host'], + $dbConfig['dbname'], + $dbConfig['charset'] ?? 'utf8mb4' + ); + $pdo = new PDO($dsn, $dbConfig['user'], $dbConfig['pass'], [ + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, + ]); + + $starStmt = $pdo->query( + "SELECT hip, proper, bayer, con, spect, mag, ci, rarad, decrad + FROM star_hipparcos + WHERE mag IS NOT NULL + AND rarad IS NOT NULL + AND decrad IS NOT NULL + AND mag <= " . (float) $starDatabaseMagnitudeLimit . " + ORDER BY mag ASC" + ); + + while ($row = $starStmt->fetch()) { + $starData[] = [ + 'hip' => $row['hip'] !== null ? (int) $row['hip'] : null, + 'proper' => trim((string) ($row['proper'] ?? '')), + 'bayer' => trim((string) ($row['bayer'] ?? '')), + 'con' => trim((string) ($row['con'] ?? '')), + 'spect' => trim((string) ($row['spect'] ?? '')), + 'ra' => (float) $row['rarad'], + 'dec' => (float) $row['decrad'], + 'mag' => (float) $row['mag'], + 'ci' => $row['ci'] !== null ? (float) $row['ci'] : null, + ]; + } + + $constellationIndex = []; + $ensureConstellation = static function (string $id) use (&$constellationIndex): void { + if (!isset($constellationIndex[$id])) { + $constellationIndex[$id] = [ + 'id' => $id, + 'lines' => [], + 'label_positions' => [], + 'common_name' => [ + 'german' => null, + 'native' => null, + 'byname' => null, + ], + ]; + } + }; + + $nameStmt = $pdo->query( + "SELECT constellation_id, german_name, native_name, byname + FROM constellation_names + ORDER BY constellation_id" + ); + foreach ($nameStmt as $row) { + $constellationId = (string) $row['constellation_id']; + $ensureConstellation($constellationId); + $constellationIndex[$constellationId]['common_name'] = [ + 'german' => $row['german_name'] !== null ? (string) $row['german_name'] : null, + 'native' => $row['native_name'] !== null ? (string) $row['native_name'] : null, + 'byname' => $row['byname'] !== null ? (string) $row['byname'] : null, + ]; + } + + $labelStmt = $pdo->query( + "SELECT constellation_id, position_index, ra_hours, dec_degrees + FROM constellation_label_positions + ORDER BY constellation_id, position_index" + ); + foreach ($labelStmt as $row) { + $constellationId = (string) $row['constellation_id']; + $ensureConstellation($constellationId); + $constellationIndex[$constellationId]['label_positions'][] = [ + (float) $row['ra_hours'], + (float) $row['dec_degrees'], + ]; + } + + $lineStmt = $pdo->query( + "SELECT cl.constellation_id, cl.line_index, clp.point_index, clp.hip_number + FROM constellation_lines cl + INNER JOIN constellation_line_points clp ON clp.line_id = cl.id + ORDER BY cl.constellation_id, cl.line_index, clp.point_index" + ); + + foreach ($lineStmt as $row) { + $constellationId = (string) $row['constellation_id']; + $lineIndex = (int) $row['line_index']; + + $ensureConstellation($constellationId); + + $constellationIndex[$constellationId]['lines'][$lineIndex] ??= []; + $constellationIndex[$constellationId]['lines'][$lineIndex][] = (int) $row['hip_number']; + } + + foreach ($constellationIndex as &$constellation) { + if ($constellation['lines']) { + ksort($constellation['lines']); + $constellation['lines'] = array_values($constellation['lines']); + } + } + unset($constellation); + + $constellationLines = array_values($constellationIndex); + + $lineStarStmt = $pdo->query( + "SELECT DISTINCT sh.hip, sh.proper, sh.bayer, sh.con, sh.spect, sh.mag, sh.ci, sh.rarad, sh.decrad + FROM constellation_line_points clp + INNER JOIN star_hipparcos sh ON sh.hip = clp.hip_number + WHERE sh.rarad IS NOT NULL + AND sh.decrad IS NOT NULL" + ); + + while ($row = $lineStarStmt->fetch()) { + $lineStarData[] = [ + 'hip' => $row['hip'] !== null ? (int) $row['hip'] : null, + 'proper' => trim((string) ($row['proper'] ?? '')), + 'bayer' => trim((string) ($row['bayer'] ?? '')), + 'con' => trim((string) ($row['con'] ?? '')), + 'spect' => trim((string) ($row['spect'] ?? '')), + 'ra' => (float) $row['rarad'], + 'dec' => (float) $row['decrad'], + 'mag' => $row['mag'] !== null ? (float) $row['mag'] : 99.0, + 'ci' => $row['ci'] !== null ? (float) $row['ci'] : null, + ]; + } + + $messierStmt = $pdo->query( + "SELECT messier, name, object_type_abrev, ra_deg, dec_deg, v_mag, major_axis + FROM dso_objects + WHERE messier IS NOT NULL + AND messier <> '' + AND ra_deg IS NOT NULL + AND dec_deg IS NOT NULL + AND v_mag IS NOT NULL + AND v_mag <= " . (float) $messierDatabaseMagnitudeLimit . " + ORDER BY v_mag ASC, CAST(REGEXP_REPLACE(messier, '[^0-9]', '') AS UNSIGNED)" + ); + + while ($row = $messierStmt->fetch()) { + if (!preg_match('/(\d+)/', (string) $row['messier'], $match)) { + continue; + } + + $messierData[] = [ + 'm' => (int) $match[1], + 'name' => trim((string) ($row['name'] ?? '')), + 'type' => trim((string) ($row['object_type_abrev'] ?? 'As')), + 'ra' => ((float) $row['ra_deg']) / 15.0, + 'dec' => (float) $row['dec_deg'], + 'mag' => (float) $row['v_mag'], + 'size' => $row['major_axis'] !== null ? (float) $row['major_axis'] : 8.0, + ]; + } + + $meteorStmt = $pdo->query( + "SELECT designation, shower_code, radiant_delta_deg, peak_sol, zhr, zhr_variable + FROM meteorshowers + WHERE peak_sol IS NOT NULL + ORDER BY peak_sol ASC, designation ASC" + ); + + while ($row = $meteorStmt->fetch()) { + $meteorShowerPeaks[] = [ + 'designation' => trim((string) ($row['designation'] ?? '')), + 'shower_code' => trim((string) ($row['shower_code'] ?? '')), + 'radiant_delta_deg' => $row['radiant_delta_deg'] !== null ? (float) $row['radiant_delta_deg'] : null, + 'peak_sol' => (float) $row['peak_sol'], + 'zhr' => $row['zhr'] !== null ? (int) $row['zhr'] : null, + 'zhr_variable' => trim((string) ($row['zhr_variable'] ?? '')), + ]; + } +} catch (Throwable $e) { + $starDataError = $e->getMessage(); +} require __DIR__ . '/header.php'; @@ -51,12 +366,18 @@ $chartLongitude = isset($currentLocation['longitude']) ? (float) $currentLocatio $chartElevation = isset($currentLocation['elevation']) && $currentLocation['elevation'] !== null ? (float) $currentLocation['elevation'] : 640.0; $chartTimezone = (string) (($currentLocation['timezone'] ?? null) ?: 'Europe/Berlin'); +$monthTime = $monthSkyTimes[$selectedMonth]; +$chartLocalTime = new DateTimeImmutable( + sprintf('%04d-%02d-15 %02d:%02d:00', $selectedYear, $selectedMonth, $monthTime['hour'], $monthTime['minute']), + new DateTimeZone($chartTimezone) +); +$chartUtcTime = $chartLocalTime->setTimezone(new DateTimeZone('UTC')); + $pythonExecutable = PHP_OS_FAMILY === 'Windows' ? 'python' : '/usr/bin/python'; $pythonScriptPath = __DIR__ . '/py/api.py'; $pythonOutput = []; $pythonExitCode = 0; -$pythonPayload = null; -$pythonError = null; +$planetErrorText = null; $pythonArgs = [ $pythonScriptPath, @@ -66,327 +387,2950 @@ $pythonArgs = [ (string) $chartElevation, (string) $selectedYear, (string) $selectedMonth, - (string) ((int) $selectedSkyTime['hour']), - (string) ((int) $selectedSkyTime['minute']), + (string) $monthTime['hour'], + (string) $monthTime['minute'], $chartTimezone, ]; -$command = $pythonExecutable . ' ' . implode(' ', array_map('escapeshellarg', $pythonArgs)) . ' 2>&1'; -exec($command, $pythonOutput, $pythonExitCode); - +$pythonCommand = $pythonExecutable . ' ' . implode(' ', array_map('escapeshellarg', $pythonArgs)) . ' 2>&1'; +exec($pythonCommand, $pythonOutput, $pythonExitCode); $pythonRaw = trim(implode("\n", $pythonOutput)); -if ($pythonExitCode !== 0) { - $pythonError = $pythonRaw !== '' ? $pythonRaw : 'Python-Aufruf fehlgeschlagen.'; -} else { - $decoded = json_decode($pythonRaw, true); - if (!is_array($decoded)) { - $pythonError = $pythonRaw !== '' ? $pythonRaw : 'Python lieferte kein gültiges JSON.'; - } elseif (!($decoded['ok'] ?? false)) { - $pythonError = (string) ($decoded['error'] ?? 'Python meldete einen Fehler.'); - } else { - $pythonPayload = $decoded; +$pythonDecoded = json_decode($pythonRaw, true); + +if (is_array($pythonDecoded) && ($pythonDecoded['ok'] ?? false)) { + if (is_array($pythonDecoded['bodies']['moon'] ?? null)) { + $moon = $pythonDecoded['bodies']['moon']; + $moonData = [ + 'label' => (string) ($moon['label'] ?? 'Mond'), + 'az' => isset($moon['azimuth_deg']) ? (float) $moon['azimuth_deg'] : null, + 'alt' => isset($moon['altitude_deg']) ? (float) $moon['altitude_deg'] : null, + 'visible' => !empty($moon['visible']), + 'phaseFraction' => isset($moon['phase_fraction']) ? (float) $moon['phase_fraction'] : null, + 'waxing' => isset($moon['waxing']) ? (bool) $moon['waxing'] : null, + ]; + } + foreach (($pythonDecoded['bodies']['planets'] ?? []) as $planet) { + if (!is_array($planet)) { + continue; + } + $planetData[] = [ + 'key' => (string) ($planet['key'] ?? ''), + 'label' => (string) ($planet['label'] ?? ''), + 'az' => isset($planet['azimuth_deg']) ? (float) $planet['azimuth_deg'] : null, + 'alt' => isset($planet['altitude_deg']) ? (float) $planet['altitude_deg'] : null, + 'mag' => isset($planet['magnitude']) ? (float) $planet['magnitude'] : null, + 'visible' => !empty($planet['visible']), + ]; + } +} elseif ($pythonRaw !== '') { + $planetErrorText = $pythonRaw; +} + +$moonPhaseOutput = []; +$moonPhaseExitCode = 0; +$moonPhaseArgs = [ + $pythonScriptPath, + 'moon_phases_for_month', + (string) $selectedYear, + (string) $selectedMonth, + $chartTimezone, +]; +$moonPhaseCommand = $pythonExecutable . ' ' . implode(' ', array_map('escapeshellarg', $moonPhaseArgs)) . ' 2>&1'; +exec($moonPhaseCommand, $moonPhaseOutput, $moonPhaseExitCode); +$moonPhaseRaw = trim(implode("\n", $moonPhaseOutput)); +$moonPhaseDecoded = json_decode($moonPhaseRaw, true); +if (is_array($moonPhaseDecoded) && ($moonPhaseDecoded['ok'] ?? false)) { + $moonPhaseEvents = is_array($moonPhaseDecoded['phases'] ?? null) ? $moonPhaseDecoded['phases'] : []; +} + +$goldenHandleOutput = []; +$goldenHandleExitCode = 0; +$goldenHandleArgs = [ + $pythonScriptPath, + 'golden_handle_for_month', + (string) $selectedYear, + (string) $selectedMonth, + $chartTimezone, +]; +$goldenHandleCommand = $pythonExecutable . ' ' . implode(' ', array_map('escapeshellarg', $goldenHandleArgs)) . ' 2>&1'; +exec($goldenHandleCommand, $goldenHandleOutput, $goldenHandleExitCode); +$goldenHandleRaw = trim(implode("\n", $goldenHandleOutput)); +$goldenHandleDecoded = json_decode($goldenHandleRaw, true); +if (is_array($goldenHandleDecoded) && ($goldenHandleDecoded['ok'] ?? false)) { + $goldenHandleEvents = is_array($goldenHandleDecoded['events'] ?? null) ? $goldenHandleDecoded['events'] : []; +} + +$seasonOutput = []; +$seasonExitCode = 0; +$seasonArgs = [ + $pythonScriptPath, + 'season_changes_for_month', + (string) $selectedYear, + (string) $selectedMonth, + $chartTimezone, +]; +$seasonCommand = $pythonExecutable . ' ' . implode(' ', array_map('escapeshellarg', $seasonArgs)) . ' 2>&1'; +exec($seasonCommand, $seasonOutput, $seasonExitCode); +$seasonRaw = trim(implode("\n", $seasonOutput)); +$seasonDecoded = json_decode($seasonRaw, true); +if (is_array($seasonDecoded) && ($seasonDecoded['ok'] ?? false)) { + $seasonChanges = is_array($seasonDecoded['changes'] ?? null) ? $seasonDecoded['changes'] : []; +} + +$timeChangeOutput = []; +$timeChangeExitCode = 0; +$timeChangeArgs = [ + $pythonScriptPath, + 'time_changes_for_month', + (string) $selectedYear, + (string) $selectedMonth, + $chartTimezone, +]; +$timeChangeCommand = $pythonExecutable . ' ' . implode(' ', array_map('escapeshellarg', $timeChangeArgs)) . ' 2>&1'; +exec($timeChangeCommand, $timeChangeOutput, $timeChangeExitCode); +$timeChangeRaw = trim(implode("\n", $timeChangeOutput)); +$timeChangeDecoded = json_decode($timeChangeRaw, true); +if (is_array($timeChangeDecoded) && ($timeChangeDecoded['ok'] ?? false)) { + $timeChanges = is_array($timeChangeDecoded['events'] ?? null) ? $timeChangeDecoded['events'] : []; +} + +$eclipseOutput = []; +$eclipseExitCode = 0; +$eclipseArgs = [ + $pythonScriptPath, + 'eclipses_for_month', + (string) $chartLatitude, + (string) $chartLongitude, + (string) $chartElevation, + (string) $selectedYear, + (string) $selectedMonth, + $chartTimezone, +]; +$eclipseCommand = $pythonExecutable . ' ' . implode(' ', array_map('escapeshellarg', $eclipseArgs)) . ' 2>&1'; +exec($eclipseCommand, $eclipseOutput, $eclipseExitCode); +$eclipseRaw = trim(implode("\n", $eclipseOutput)); +$eclipseDecoded = json_decode($eclipseRaw, true); +if (is_array($eclipseDecoded) && ($eclipseDecoded['ok'] ?? false)) { + $eclipseEvents = is_array($eclipseDecoded['events'] ?? null) ? $eclipseDecoded['events'] : []; +} + +$moonApsisOutput = []; +$moonApsisExitCode = 0; +$moonApsisArgs = [ + $pythonScriptPath, + 'moon_apsides_for_month', + (string) $selectedYear, + (string) $selectedMonth, + $chartTimezone, +]; +$moonApsisCommand = $pythonExecutable . ' ' . implode(' ', array_map('escapeshellarg', $moonApsisArgs)) . ' 2>&1'; +exec($moonApsisCommand, $moonApsisOutput, $moonApsisExitCode); +$moonApsisRaw = trim(implode("\n", $moonApsisOutput)); +$moonApsisDecoded = json_decode($moonApsisRaw, true); +if (is_array($moonApsisDecoded) && ($moonApsisDecoded['ok'] ?? false)) { + $moonApsisEvents = is_array($moonApsisDecoded['events'] ?? null) ? $moonApsisDecoded['events'] : []; +} + +$sunApsisOutput = []; +$sunApsisExitCode = 0; +$sunApsisArgs = [ + $pythonScriptPath, + 'sun_apsides_for_month', + (string) $selectedYear, + (string) $selectedMonth, + $chartTimezone, +]; +$sunApsisCommand = $pythonExecutable . ' ' . implode(' ', array_map('escapeshellarg', $sunApsisArgs)) . ' 2>&1'; +exec($sunApsisCommand, $sunApsisOutput, $sunApsisExitCode); +$sunApsisRaw = trim(implode("\n", $sunApsisOutput)); +$sunApsisDecoded = json_decode($sunApsisRaw, true); +if (is_array($sunApsisDecoded) && ($sunApsisDecoded['ok'] ?? false)) { + $sunApsisEvents = is_array($sunApsisDecoded['events'] ?? null) ? $sunApsisDecoded['events'] : []; +} + +$elongationOutput = []; +$elongationExitCode = 0; +$elongationArgs = [ + $pythonScriptPath, + 'inner_planet_elongations_for_month', + (string) $selectedYear, + (string) $selectedMonth, + $chartTimezone, +]; +$elongationCommand = $pythonExecutable . ' ' . implode(' ', array_map('escapeshellarg', $elongationArgs)) . ' 2>&1'; +exec($elongationCommand, $elongationOutput, $elongationExitCode); +$elongationRaw = trim(implode("\n", $elongationOutput)); +$elongationDecoded = json_decode($elongationRaw, true); +if (is_array($elongationDecoded) && ($elongationDecoded['ok'] ?? false)) { + $innerPlanetElongationEvents = is_array($elongationDecoded['events'] ?? null) ? $elongationDecoded['events'] : []; +} + +$venusPeakMagnitudeOutput = []; +$venusPeakMagnitudeExitCode = 0; +$venusPeakMagnitudeArgs = [ + $pythonScriptPath, + 'venus_peak_magnitude_for_month', + (string) $selectedYear, + (string) $selectedMonth, + $chartTimezone, +]; +$venusPeakMagnitudeCommand = $pythonExecutable . ' ' . implode(' ', array_map('escapeshellarg', $venusPeakMagnitudeArgs)) . ' 2>&1'; +exec($venusPeakMagnitudeCommand, $venusPeakMagnitudeOutput, $venusPeakMagnitudeExitCode); +$venusPeakMagnitudeRaw = trim(implode("\n", $venusPeakMagnitudeOutput)); +$venusPeakMagnitudeDecoded = json_decode($venusPeakMagnitudeRaw, true); +if (is_array($venusPeakMagnitudeDecoded) && ($venusPeakMagnitudeDecoded['ok'] ?? false)) { + $venusPeakMagnitudeEvents = is_array($venusPeakMagnitudeDecoded['events'] ?? null) ? $venusPeakMagnitudeDecoded['events'] : []; +} + +$outerPlanetOutput = []; +$outerPlanetExitCode = 0; +$outerPlanetArgs = [ + $pythonScriptPath, + 'outer_planet_events_for_month', + (string) $selectedYear, + (string) $selectedMonth, + $chartTimezone, +]; +$outerPlanetCommand = $pythonExecutable . ' ' . implode(' ', array_map('escapeshellarg', $outerPlanetArgs)) . ' 2>&1'; +exec($outerPlanetCommand, $outerPlanetOutput, $outerPlanetExitCode); +$outerPlanetRaw = trim(implode("\n", $outerPlanetOutput)); +$outerPlanetDecoded = json_decode($outerPlanetRaw, true); +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 = [ + $pythonScriptPath, + 'moon_planet_approaches', + (string) $chartLatitude, + (string) $chartLongitude, + (string) $chartElevation, + (string) $selectedYear, + (string) $selectedMonth, + $chartTimezone, + (string) $moonPlanetMaxSeparationDeg, +]; +$moonPlanetCommand = $pythonExecutable . ' ' . implode(' ', array_map('escapeshellarg', $moonPlanetArgs)) . ' 2>&1'; +exec($moonPlanetCommand, $moonPlanetOutput, $moonPlanetExitCode); +$moonPlanetRaw = trim(implode("\n", $moonPlanetOutput)); +$moonPlanetDecoded = json_decode($moonPlanetRaw, true); +if (is_array($moonPlanetDecoded) && ($moonPlanetDecoded['ok'] ?? false)) { + $moonPlanetApproaches = is_array($moonPlanetDecoded['approaches'] ?? null) ? $moonPlanetDecoded['approaches'] : []; +} + +$planetConjunctionOutput = []; +$planetConjunctionExitCode = 0; +$planetConjunctionArgs = [ + $pythonScriptPath, + 'planet_conjunctions_for_month', + (string) $chartLatitude, + (string) $chartLongitude, + (string) $chartElevation, + (string) $selectedYear, + (string) $selectedMonth, + $chartTimezone, + (string) $planetConjunctionMaxSeparationDeg, +]; +$planetConjunctionCommand = $pythonExecutable . ' ' . implode(' ', array_map('escapeshellarg', $planetConjunctionArgs)) . ' 2>&1'; +exec($planetConjunctionCommand, $planetConjunctionOutput, $planetConjunctionExitCode); +$planetConjunctionRaw = trim(implode("\n", $planetConjunctionOutput)); +$planetConjunctionDecoded = json_decode($planetConjunctionRaw, true); +if (is_array($planetConjunctionDecoded) && ($planetConjunctionDecoded['ok'] ?? false)) { + $planetConjunctions = is_array($planetConjunctionDecoded['conjunctions'] ?? null) ? $planetConjunctionDecoded['conjunctions'] : []; +} + +$moonOccultationStars = []; +try { + if (isset($pdo)) { + $stmtOccultStars = $pdo->prepare( + "SELECT hip, proper, bayer, con, mag, rarad, decrad, dist + FROM star_hipparcos + WHERE rarad IS NOT NULL + AND decrad IS NOT NULL + AND mag IS NOT NULL + AND mag <= :max_mag + ORDER BY mag ASC, proper ASC, bayer ASC, hip ASC" + ); + $stmtOccultStars->execute([':max_mag' => $moonOccultationMaxMag]); + + foreach ($stmtOccultStars->fetchAll() as $row) { + $moonOccultationStars[] = [ + 'hip' => isset($row['hip']) ? (int) $row['hip'] : 0, + 'label' => buildMonthOccultationStarLabel($row), + 'constellation' => trim((string) ($row['con'] ?? '')), + 'mag' => isset($row['mag']) ? (float) $row['mag'] : 99.0, + 'ra' => isset($row['rarad']) ? (float) $row['rarad'] * (12 / M_PI) : 0.0, + 'dec' => isset($row['decrad']) ? (float) $row['decrad'] * (180 / M_PI) : 0.0, + 'distLy' => isset($row['dist']) && $row['dist'] !== null && (float) $row['dist'] > 0.0 + ? (float) $row['dist'] * 3.26156 + : 1000.0, + ]; + } + } +} catch (Throwable $e) { +} + +if ($moonOccultationStars) { + $tempOccultationFile = tempnam(sys_get_temp_dir(), 'month_occult_'); + if ($tempOccultationFile !== false) { + try { + file_put_contents($tempOccultationFile, json_encode($moonOccultationStars, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR)); + $moonOccultOutput = []; + $moonOccultExitCode = 0; + $moonOccultArgs = [ + $pythonScriptPath, + 'moon_star_occultations_for_month', + (string) $chartLatitude, + (string) $chartLongitude, + (string) $chartElevation, + (string) $selectedYear, + (string) $selectedMonth, + $chartTimezone, + $tempOccultationFile, + ]; + $moonOccultCommand = $pythonExecutable . ' ' . implode(' ', array_map('escapeshellarg', $moonOccultArgs)) . ' 2>&1'; + exec($moonOccultCommand, $moonOccultOutput, $moonOccultExitCode); + $moonOccultRaw = trim(implode("\n", $moonOccultOutput)); + $moonOccultDecoded = json_decode($moonOccultRaw, true); + if (is_array($moonOccultDecoded) && ($moonOccultDecoded['ok'] ?? false)) { + $moonOccultations = is_array($moonOccultDecoded['results'] ?? null) ? $moonOccultDecoded['results'] : []; + } + } catch (Throwable $e) { + } finally { + if (is_file($tempOccultationFile)) { + @unlink($tempOccultationFile); + } + } } } -$bodyRows = []; -if (is_array($pythonPayload['bodies'] ?? null)) { - $bodyRows[] = $pythonPayload['bodies']['sun'] ?? null; - $bodyRows[] = $pythonPayload['bodies']['moon'] ?? null; - foreach (($pythonPayload['bodies']['planets'] ?? []) as $planet) { - $bodyRows[] = $planet; +foreach ($moonPhaseEvents as $phaseEvent) { + if (!is_array($phaseEvent)) { + continue; } - $bodyRows = array_values(array_filter($bodyRows, static fn ($row) => is_array($row))); + + $monthEventList[] = [ + 'event' => (string) ($phaseEvent['label'] ?? ''), + 'date' => (string) ($phaseEvent['local_date'] ?? ''), + 'time' => (string) ($phaseEvent['local_time'] ?? ''), + 'local_iso' => (string) ($phaseEvent['local_iso'] ?? ''), + 'type' => 'moon_phase', + ]; } -$formatNumber = static function ($value, int $decimals = 1): string { - if (!is_numeric($value)) { - return '–'; +foreach ($goldenHandleEvents as $goldenHandleEvent) { + if (!is_array($goldenHandleEvent)) { + continue; } - return number_format((float) $value, $decimals, ',', ''); -}; + + $peakDate = (string) ($goldenHandleEvent['peak_local_date'] ?? ''); + $peakTime = (string) ($goldenHandleEvent['peak_local_time'] ?? ''); + $goldenHandleLabel = 'Goldener Henkel am Mond'; + + $monthEventList[] = [ + 'event' => $goldenHandleLabel, + 'date' => $peakDate !== '' ? $peakDate : (string) ($goldenHandleEvent['local_date'] ?? ''), + 'time' => $peakTime !== '' ? $peakTime : (string) ($goldenHandleEvent['local_time'] ?? ''), + 'local_iso' => (string) ($goldenHandleEvent['peak_local_iso'] ?? $goldenHandleEvent['local_iso'] ?? ''), + 'type' => 'golden_handle', + ]; +} + +foreach ($seasonChanges as $seasonChange) { + if (!is_array($seasonChange)) { + continue; + } + + $monthEventList[] = [ + 'event' => (string) ($seasonChange['label'] ?? 'Jahreszeitenwechsel'), + 'date' => (string) ($seasonChange['local_date'] ?? ''), + 'time' => (string) ($seasonChange['local_time'] ?? ''), + 'local_iso' => (string) ($seasonChange['local_iso'] ?? ''), + 'type' => 'season_change', + ]; +} + +foreach ($timeChanges as $timeChange) { + if (!is_array($timeChange)) { + continue; + } + + $timeChangeKind = (string) ($timeChange['kind'] ?? ''); + $timeChangeLabel = $timeChangeKind === 'dst_start' + ? 'Beginn der Sommerzeit (Uhren von 02:00 auf 03:00 Uhr vorstellen)' + : ($timeChangeKind === 'dst_end' + ? 'Beginn der Normalzeit (Uhren von 03:00 auf 02:00 Uhr zurueckstellen)' + : 'Zeitumstellung'); + $timeChangeTime = $timeChangeKind === 'dst_start' + ? '02:00' + : ($timeChangeKind === 'dst_end' ? '03:00' : (string) ($timeChange['local_time'] ?? '')); + + $monthEventList[] = [ + 'event' => $timeChangeLabel, + 'date' => (string) ($timeChange['local_date'] ?? ''), + 'time' => $timeChangeTime, + 'local_iso' => (string) ($timeChange['local_iso'] ?? ''), + 'type' => 'time_change', + ]; +} + +foreach ($eclipseEvents as $eclipseEvent) { + if (!is_array($eclipseEvent)) { + continue; + } + + $monthEventList[] = [ + 'event' => (string) ($eclipseEvent['label'] ?? 'Finsternis'), + 'date' => (string) ($eclipseEvent['local_date'] ?? ''), + 'time' => (string) ($eclipseEvent['local_time'] ?? ''), + 'local_iso' => (string) ($eclipseEvent['local_iso'] ?? ''), + 'type' => (string) ($eclipseEvent['category'] ?? 'eclipse'), + ]; +} + +foreach ($moonApsisEvents as $moonApsisEvent) { + if (!is_array($moonApsisEvent)) { + continue; + } + + $moonApsisKind = (string) ($moonApsisEvent['kind'] ?? ''); + $distanceText = isset($moonApsisEvent['distance_km']) + ? number_format((float) $moonApsisEvent['distance_km'], 0, ',', '.') . ' km' + : ''; + $moonApsisLabel = $moonApsisKind === 'perigee' + ? 'Mond in Erdnähe (Perigäum)' + : ($moonApsisKind === 'apogee' ? 'Mond in Erdferne (Apogäum)' : (string) ($moonApsisEvent['label'] ?? 'Mondabstand')); + + $monthEventList[] = [ + 'event' => trim($moonApsisLabel . ($distanceText !== '' ? ' (' . $distanceText . ')' : '')), + 'date' => (string) ($moonApsisEvent['local_date'] ?? ''), + 'time' => (string) ($moonApsisEvent['local_time'] ?? ''), + 'local_iso' => (string) ($moonApsisEvent['local_iso'] ?? ''), + 'type' => 'moon_apsis', + ]; +} + +foreach ($sunApsisEvents as $sunApsisEvent) { + if (!is_array($sunApsisEvent)) { + continue; + } + + $sunApsisKind = (string) ($sunApsisEvent['kind'] ?? ''); + $distanceText = isset($sunApsisEvent['distance_km']) + ? number_format((float) $sunApsisEvent['distance_km'], 0, ',', '.') . ' km' + : ''; + $sunApsisLabel = $sunApsisKind === 'perihelion' + ? 'Erde erreicht sonnennächsten Punkt (Perihel)' + : ($sunApsisKind === 'aphelion' ? 'Erde erreicht sonnenfernsten Punkt (Aphel)' : (string) ($sunApsisEvent['label'] ?? 'Sonnenabstand')); + + $monthEventList[] = [ + 'event' => trim($sunApsisLabel . ($distanceText !== '' ? ' (' . $distanceText . ')' : '')), + 'date' => (string) ($sunApsisEvent['local_date'] ?? ''), + 'time' => (string) ($sunApsisEvent['local_time'] ?? ''), + 'local_iso' => (string) ($sunApsisEvent['local_iso'] ?? ''), + 'type' => 'sun_apsis', + ]; +} + +foreach ($innerPlanetElongationEvents as $elongationEvent) { + if (!is_array($elongationEvent)) { + continue; + } + + $elongationPlanet = (string) ($elongationEvent['planet_label'] ?? 'Planet'); + $elongationKind = (string) ($elongationEvent['kind'] ?? ''); + $elongationText = isset($elongationEvent['elongation_deg']) + ? number_format((float) $elongationEvent['elongation_deg'], 2, ',', '') . ' Grad' + : ''; + $elongationLabel = $elongationKind === 'western' + ? $elongationPlanet . ' größte westliche Elongation' + : ($elongationKind === 'eastern' + ? $elongationPlanet . ' größte östliche Elongation' + : (string) ($elongationEvent['label'] ?? 'Elongation')); + + $monthEventList[] = [ + 'event' => trim($elongationLabel . ($elongationText !== '' ? ' (' . $elongationText . ')' : '')), + 'date' => (string) ($elongationEvent['local_date'] ?? ''), + 'time' => (string) ($elongationEvent['local_time'] ?? ''), + 'local_iso' => (string) ($elongationEvent['local_iso'] ?? ''), + 'type' => 'planet_elongation', + ]; +} + +foreach ($venusPeakMagnitudeEvents as $venusPeakMagnitudeEvent) { + if (!is_array($venusPeakMagnitudeEvent)) { + continue; + } + + $magnitudeText = isset($venusPeakMagnitudeEvent['mag']) + ? number_format((float) $venusPeakMagnitudeEvent['mag'], 1, ',', '') . ' mag' + : ''; + + $monthEventList[] = [ + 'event' => trim('Venus in groesster Helligkeit' . ($magnitudeText !== '' ? ' (' . $magnitudeText . ')' : '')), + 'date' => (string) ($venusPeakMagnitudeEvent['local_date'] ?? ''), + 'time' => (string) ($venusPeakMagnitudeEvent['local_time'] ?? ''), + 'local_iso' => (string) ($venusPeakMagnitudeEvent['local_iso'] ?? ''), + 'type' => 'venus_peak_magnitude', + ]; +} + +foreach ($outerPlanetEvents as $outerPlanetEvent) { + if (!is_array($outerPlanetEvent)) { + continue; + } + + $monthEventList[] = [ + 'event' => (string) ($outerPlanetEvent['label'] ?? 'Planetenereignis'), + 'date' => (string) ($outerPlanetEvent['local_date'] ?? ''), + 'time' => (string) ($outerPlanetEvent['local_time'] ?? ''), + 'local_iso' => (string) ($outerPlanetEvent['local_iso'] ?? ''), + 'type' => 'outer_planet_event', + ]; +} + +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; + } + + $distanceText = isset($approachEvent['separation_deg']) + ? number_format((float) $approachEvent['separation_deg'], 2, ',', '') . ' Grad' + : ''; + + $monthEventList[] = [ + 'event' => trim((string) ($approachEvent['label'] ?? 'Mond nahe Planet') . ($distanceText !== '' ? ' (' . $distanceText . ')' : '')), + 'date' => (string) ($approachEvent['local_date'] ?? ''), + 'time' => (string) ($approachEvent['local_time'] ?? ''), + 'local_iso' => (string) ($approachEvent['local_iso'] ?? ''), + 'type' => 'moon_planet', + ]; +} + +foreach ($planetConjunctions as $conjunctionEvent) { + if (!is_array($conjunctionEvent)) { + continue; + } + + $distanceText = isset($conjunctionEvent['separation_deg']) + ? number_format((float) $conjunctionEvent['separation_deg'], 2, ',', '') . ' Grad' + : ''; + + $monthEventList[] = [ + 'event' => trim((string) ($conjunctionEvent['label'] ?? 'Planetenkonjunktion') . ($distanceText !== '' ? ' (' . $distanceText . ')' : '')), + 'date' => (string) ($conjunctionEvent['local_date'] ?? ''), + 'time' => (string) ($conjunctionEvent['local_time'] ?? ''), + 'local_iso' => (string) ($conjunctionEvent['local_iso'] ?? ''), + 'type' => 'planet_conjunction', + ]; +} + +foreach ($moonOccultations as $occultationEvent) { + if (!is_array($occultationEvent)) { + continue; + } + + $maximum = is_array($occultationEvent['maximum'] ?? null) ? $occultationEvent['maximum'] : null; + $star = is_array($occultationEvent['star'] ?? null) ? $occultationEvent['star'] : null; + if ($maximum === null || $star === null) { + continue; + } + + $starLabel = (string) ($star['label'] ?? 'Stern'); + $starMag = isset($star['mag']) ? number_format((float) $star['mag'], 1, ',', '') : ''; + $monthEventList[] = [ + 'event' => 'Mond bedeckt ' . $starLabel . ($starMag !== '' ? ' (' . $starMag . ' mag)' : ''), + 'date' => (string) ($maximum['local_date'] ?? ''), + 'time' => (string) ($maximum['local_time'] ?? ''), + 'local_iso' => (string) ($maximum['local_iso'] ?? ''), + 'type' => 'moon_occultation', + ]; +} + +$monthStartLocal = new DateTimeImmutable( + sprintf('%04d-%02d-01 00:00:00', $selectedYear, $selectedMonth), + new DateTimeZone($chartTimezone) +); +$monthEndLocal = $monthStartLocal->modify('first day of next month'); + +foreach ($meteorShowerPeaks as $meteorShowerPeak) { + if (!monthForecastRadiantIsVisible($chartLatitude, $meteorShowerPeak['radiant_delta_deg'] ?? null)) { + continue; + } + + $peakSol = isset($meteorShowerPeak['peak_sol']) ? (float) $meteorShowerPeak['peak_sol'] : null; + if ($peakSol === null) { + continue; + } + + $peakDateTime = findMonthForecastSolarLongitudeCrossing( + $monthStartLocal, + $monthEndLocal, + $peakSol, + new DateTimeZone($chartTimezone) + ); + if (!$peakDateTime instanceof DateTimeImmutable) { + continue; + } + + $designation = trim((string) ($meteorShowerPeak['designation'] ?? '')); + $showerCode = trim((string) ($meteorShowerPeak['shower_code'] ?? '')); + $zhr = $meteorShowerPeak['zhr'] ?? null; + $zhrVariable = trim((string) ($meteorShowerPeak['zhr_variable'] ?? '')); + $rateText = ''; + + if (is_int($zhr) && $zhr >= 0) { + $rateText = 'ZHR ca. ' . $zhr; + } elseif ($zhrVariable !== '') { + $rateText = 'ZHR ca. ' . $zhrVariable; + } + + $labelParts = []; + $labelParts[] = 'Maximum ' . ($designation !== '' ? $designation : 'Meteorstrom'); + if ($showerCode !== '') { + $labelParts[] = $showerCode; + } + if ($rateText !== '') { + $labelParts[] = $rateText; + } + + $monthEventList[] = [ + 'event' => implode(' - ', $labelParts), + 'date' => $peakDateTime->format('d.m.Y'), + 'time' => $peakDateTime->format('H:i'), + 'local_iso' => $peakDateTime->format(DateTimeInterface::ATOM), + 'type' => 'meteor_shower_peak', + ]; +} + +usort( + $monthEventList, + static fn (array $a, array $b): int => strcmp((string) ($a['local_iso'] ?? ''), (string) ($b['local_iso'] ?? '')) +); + +$chartConfig = [ + 'monthName' => $months[$selectedMonth], + 'year' => $selectedYear, + 'month' => $selectedMonth, + 'location' => [ + 'name' => $chartLocationName, + 'latitude' => $chartLatitude, + 'longitude' => $chartLongitude, + 'elevation' => $chartElevation, + 'timezone' => $chartTimezone, + ], + 'localTimeLabel' => $chartLocalTime->format('d.m.Y H:i') . ' ' . $chartTimezone, + 'utcTimeIso' => $chartUtcTime->format(DateTimeInterface::ATOM), + 'monthSkyHour' => $monthTime['hour'], + 'monthSkyMinute' => $monthTime['minute'], + 'starLimit' => $starDisplayMagnitudeLimit, + 'starLabelLimit' => $starLabelMagnitudeLimit, + 'messierLimit' => $messierDisplayMagnitudeLimit, +]; + +$chartConfigJson = json_encode($chartConfig, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); +$starDataJson = json_encode($starData, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); +$lineStarDataJson = json_encode($lineStarData, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); +$constellationLinesJson = json_encode($constellationLines, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); +$messierDataJson = json_encode($messierData, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); +$planetDataJson = json_encode($planetData, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); +$moonDataJson = json_encode($moonData, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); +$moonPhaseEventsJson = json_encode($moonPhaseEvents, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); +$starDataErrorJson = json_encode($starDataError, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); ?> +
+
+
+
+

Zeitraum waehlen

+

Die Sternkarte wird fuer den 15. des gewaehlten Monats mit der festen Monatsuhrzeit erzeugt.

+
+ + + + + +
+ +
+
+ +
+
+
+

Zenitprojektion

+

+ , + +

+
+
+ +
+ +
+ + +
+ +
+
+
+

Südhimmel

+

+ , + +

+
+
+ +
+ +
+ + +
+ +
+
+
+

Mondphasen Im

+

Eine Monatszeile vom ersten bis zum letzten Tag mit der Mondtextur aus moon4geocron.png.

+
+
+ +
+
+ Mondphasen des Monats in einer Zeile +
+
+ + +
+ +
+
+
+

Ereignisse

+

Chronologische Monatsliste fuer

+
+
+ + +
+ +
+
+ -
-
-
-
-
- - -
+ + diff --git a/public/monatsvorhersage_old.php b/public/monatsvorhersage_old.php index b82d2f6..659d336 100644 --- a/public/monatsvorhersage_old.php +++ b/public/monatsvorhersage_old.php @@ -67,6 +67,7 @@ $innerPlanetElongationEvents = []; $venusPeakMagnitudeEvents = []; $outerPlanetEvents = []; $outerPlanetStationEvents = []; +$jupiterMoonSideEvents = []; $moonOccultations = []; $meteorShowerPeaks = []; $seasonChanges = []; @@ -616,6 +617,23 @@ if (is_array($outerPlanetStationDecoded) && ($outerPlanetStationDecoded['ok'] ?? $outerPlanetStationEvents = is_array($outerPlanetStationDecoded['events'] ?? null) ? $outerPlanetStationDecoded['events'] : []; } +$jupiterMoonSideOutput = []; +$jupiterMoonSideExitCode = 0; +$jupiterMoonSideArgs = [ + $pythonScriptPath, + 'jupiter_moons_one_side_for_month', + (string) $selectedYear, + (string) $selectedMonth, + $chartTimezone, +]; +$jupiterMoonSideCommand = $pythonExecutable . ' ' . implode(' ', array_map('escapeshellarg', $jupiterMoonSideArgs)) . ' 2>&1'; +exec($jupiterMoonSideCommand, $jupiterMoonSideOutput, $jupiterMoonSideExitCode); +$jupiterMoonSideRaw = trim(implode("\n", $jupiterMoonSideOutput)); +$jupiterMoonSideDecoded = json_decode($jupiterMoonSideRaw, true); +if (is_array($jupiterMoonSideDecoded) && ($jupiterMoonSideDecoded['ok'] ?? false)) { + $jupiterMoonSideEvents = is_array($jupiterMoonSideDecoded['events'] ?? null) ? $jupiterMoonSideDecoded['events'] : []; +} + $moonPlanetOutput = []; $moonPlanetExitCode = 0; $moonPlanetArgs = [ @@ -930,6 +948,26 @@ foreach ($outerPlanetStationEvents as $outerPlanetStationEvent) { ]; } +foreach ($jupiterMoonSideEvents as $jupiterMoonSideEvent) { + if (!is_array($jupiterMoonSideEvent)) { + continue; + } + + $durationLabel = trim((string) ($jupiterMoonSideEvent['duration_label'] ?? '')); + $eventLabel = trim((string) ($jupiterMoonSideEvent['label'] ?? 'Alle 4 Jupitermonde auf einer Seite von Jupiter')); + if ($durationLabel !== '') { + $eventLabel .= ' (' . $durationLabel . ')'; + } + + $monthEventList[] = [ + 'event' => $eventLabel, + 'date' => (string) ($jupiterMoonSideEvent['local_date'] ?? ''), + 'time' => (string) ($jupiterMoonSideEvent['local_time'] ?? ''), + 'local_iso' => (string) ($jupiterMoonSideEvent['local_iso'] ?? ''), + 'type' => 'jupiter_moons_one_side', + ]; +} + foreach ($moonPlanetApproaches as $approachEvent) { if (!is_array($approachEvent)) { continue; diff --git a/public/py/__pycache__/api.cpython-312.pyc b/public/py/__pycache__/api.cpython-312.pyc index 2b43f51197a57d03776466eb4a921562100ec4b3..8ccc3cd0ba9bf8d8bd47e905e7cb91d1441e5872 100644 GIT binary patch literal 142605 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

268oRy`r907GYeD!rDCE7x9;Y<; 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 l9WB4>Dc{2K*;@JXHIkBcN7oykq6viX diff --git a/public/py/api.py b/public/py/api.py index 4208e1f..875adf2 100644 --- a/public/py/api.py +++ b/public/py/api.py @@ -1759,6 +1759,326 @@ def action_outer_planet_stations_for_month(args: list[str]) -> dict: } +def action_jupiter_moons_one_side_for_month(args: list[str]) -> dict: + if len(args) != 3: + fail( + "Aktion jupiter_moons_one_side_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_step = timedelta(minutes=10) + x_tolerance_au = 1.0e-6 + jupiter_radius_au = astronomy.JUPITER_EQUATORIAL_RADIUS_KM / astronomy.KM_PER_AU + moon_keys = ["io", "europa", "ganymede", "callisto"] + + def classify_side(dt_utc: datetime) -> tuple[str | None, str | None, dict[str, float], dict[str, float]]: + time_value = dt_to_time(dt_utc) + rotation_eqj_to_ecl = astronomy.Rotation_EQJ_ECL() + jupiter_geo = astronomy.GeoVector(astronomy.Body.Jupiter, time_value, True) + jupiter_helio = astronomy.HelioVector(astronomy.Body.Jupiter, time_value) + moon_states = astronomy.JupiterMoons(time_value) + to_earth_eqj = astronomy.Vector( + -float(jupiter_geo.x), + -float(jupiter_geo.y), + -float(jupiter_geo.z), + time_value, + ) + to_earth_ecl = astronomy.RotateVector(rotation_eqj_to_ecl, to_earth_eqj) + + camera_x = float(to_earth_ecl.x) + camera_y = float(to_earth_ecl.z) + camera_z = -float(to_earth_ecl.y) + camera_len = math.sqrt( + (camera_x * camera_x) + (camera_y * camera_y) + (camera_z * camera_z) + ) + if camera_len <= 1.0e-12: + return None, None, {}, {} + + # Dieselbe Welt-/Kameraorientierung wie in jupitersystem.php: + # Display-Koordinaten sind (x, z, -y), die Kamera schaut aus der Richtung "toEarth". + forward_x = -camera_x / camera_len + forward_y = -camera_y / camera_len + forward_z = -camera_z / camera_len + + up_x = 0.0 + up_y = 1.0 + up_z = 0.0 + + right_x = (up_y * forward_z) - (up_z * forward_y) + right_y = (up_z * forward_x) - (up_x * forward_z) + right_z = (up_x * forward_y) - (up_y * forward_x) + right_len = math.sqrt( + (right_x * right_x) + (right_y * right_y) + (right_z * right_z) + ) + if right_len <= 1.0e-12: + return None, None, {}, {} + + right_x /= right_len + right_y /= right_len + right_z /= right_len + + # Himmlische Ostrichtung am Jupiter-Ort fuer die textliche Richtung oestlich/westlich. + jx = float(jupiter_geo.x) + jy = float(jupiter_geo.y) + east_eqj_x = -jy + east_eqj_y = jx + east_eqj_z = 0.0 + east_eqj_len = math.sqrt( + (east_eqj_x * east_eqj_x) + (east_eqj_y * east_eqj_y) + (east_eqj_z * east_eqj_z) + ) + if east_eqj_len <= 1.0e-12: + return None, None, {}, {} + + east_eqj_x /= east_eqj_len + east_eqj_y /= east_eqj_len + east_eqj_z /= east_eqj_len + + east_ecl = astronomy.RotateVector( + rotation_eqj_to_ecl, + astronomy.Vector(east_eqj_x, east_eqj_y, east_eqj_z, time_value), + ) + east_display_x = float(east_ecl.x) + east_display_y = float(east_ecl.z) + east_display_z = -float(east_ecl.y) + east_screen_x = ( + (east_display_x * right_x) + + (east_display_y * right_y) + + (east_display_z * right_z) + ) + + sun_to_jupiter_x = float(jupiter_helio.x) + sun_to_jupiter_y = float(jupiter_helio.y) + sun_to_jupiter_z = float(jupiter_helio.z) + sun_to_jupiter_len = math.sqrt( + (sun_to_jupiter_x * sun_to_jupiter_x) + + (sun_to_jupiter_y * sun_to_jupiter_y) + + (sun_to_jupiter_z * sun_to_jupiter_z) + ) + if sun_to_jupiter_len <= 1.0e-12: + return None, None, {}, {} + + sun_to_jupiter_x /= sun_to_jupiter_len + sun_to_jupiter_y /= sun_to_jupiter_len + sun_to_jupiter_z /= sun_to_jupiter_len + + projected_x_positions = {} + east_offsets = {} + shadow_flags = {} + for key in moon_keys: + moon_state = getattr(moon_states, key) + moon_ecl = astronomy.RotateVector(rotation_eqj_to_ecl, moon_state) + moon_display_x = float(moon_ecl.x) + moon_display_y = float(moon_ecl.z) + moon_display_z = -float(moon_ecl.y) + projected_x = ( + (moon_display_x * right_x) + + (moon_display_y * right_y) + + (moon_display_z * right_z) + ) + projected_x_positions[key] = projected_x + east_offsets[key] = projected_x * east_screen_x + + moon_x = float(moon_state.x) + moon_y = float(moon_state.y) + moon_z = float(moon_state.z) + shadow_axis_distance = ( + (moon_x * sun_to_jupiter_x) + + (moon_y * sun_to_jupiter_y) + + (moon_z * sun_to_jupiter_z) + ) + perp_x = moon_x - (shadow_axis_distance * sun_to_jupiter_x) + perp_y = moon_y - (shadow_axis_distance * sun_to_jupiter_y) + perp_z = moon_z - (shadow_axis_distance * sun_to_jupiter_z) + shadow_flags[key] = ( + shadow_axis_distance > 0.0 + and math.sqrt((perp_x * perp_x) + (perp_y * perp_y) + (perp_z * perp_z)) < jupiter_radius_au + ) + + projected_side_kind = None + minimum_positive_x = jupiter_radius_au + x_tolerance_au + + if ( + all(x_value > minimum_positive_x for x_value in projected_x_positions.values()) + and not any(shadow_flags.values()) + ): + projected_side_kind = "right" + elif ( + all(x_value < -minimum_positive_x for x_value in projected_x_positions.values()) + and not any(shadow_flags.values()) + ): + projected_side_kind = "left" + + east_west_kind = None + if all(offset > x_tolerance_au for offset in east_offsets.values()): + east_west_kind = "east" + elif all(offset < -x_tolerance_au for offset in east_offsets.values()): + east_west_kind = "west" + + return projected_side_kind, east_west_kind, projected_x_positions, east_offsets + + def refine_transition( + left_utc: datetime, + right_utc: datetime, + target_kind: str, + *, + find_start: bool, + ) -> datetime: + left = left_utc + right = right_utc + + for _ in range(32): + mid = left + (right - left) / 2 + mid_kind, _, _, _ = classify_side(mid) + if find_start: + if mid_kind == target_kind: + right = mid + else: + left = mid + else: + if mid_kind == target_kind: + left = mid + else: + right = mid + + return left + (right - left) / 2 + + def format_duration_label(duration_minutes: int) -> str: + hours, minutes = divmod(max(0, duration_minutes), 60) + if hours > 0 and minutes > 0: + return f"{hours} h {minutes:02d} min" + if hours > 0: + return f"{hours} h" + return f"{minutes} min" + + def direction_text_from_kinds(raw_kind: str | None, east_west_kind: str | None) -> str: + if east_west_kind == "east": + return "oestlich" + if east_west_kind == "west": + return "westlich" + return "westlich" if raw_kind == "left" else "oestlich" + + events: list[dict] = [] + current = utc_start + active_kind, active_east_west_kind, _, _ = classify_side(current) + active_start_utc = utc_start if active_kind is not None else None + previous_time = current + previous_kind = active_kind + current += scan_step + + while current <= utc_end: + current_kind, current_east_west_kind, _, _ = classify_side(current) + + if active_kind is None and previous_kind != current_kind and current_kind is not None: + active_kind = current_kind + active_east_west_kind = current_east_west_kind + active_start_utc = refine_transition(previous_time, current, current_kind, find_start=True) + + elif active_kind is not None and current_kind != active_kind: + active_end_utc = refine_transition(previous_time, current, active_kind, find_start=False) + clamped_start_utc = max(active_start_utc or utc_start, utc_start) + clamped_end_utc = min(active_end_utc, utc_end) + + if clamped_end_utc > clamped_start_utc: + start_local = clamped_start_utc.astimezone(tz) + end_local = clamped_end_utc.astimezone(tz) + duration_minutes = max( + 1, + int(round((clamped_end_utc - clamped_start_utc).total_seconds() / 60.0)), + ) + side_text = direction_text_from_kinds(active_kind, active_east_west_kind) + events.append({ + "kind": active_kind, + "direction": active_east_west_kind, + "label": f"Alle 4 Jupitermonde {side_text} von Jupiter", + "duration_label": format_duration_label(duration_minutes), + "duration_minutes": duration_minutes, + "utc_iso": clamped_start_utc.isoformat().replace("+00:00", "Z"), + "local_iso": start_local.isoformat(), + "local_date": start_local.strftime("%d.%m.%Y"), + "local_time": start_local.strftime("%H:%M"), + "end_utc_iso": clamped_end_utc.isoformat().replace("+00:00", "Z"), + "end_local_iso": end_local.isoformat(), + "end_local_date": end_local.strftime("%d.%m.%Y"), + "end_local_time": end_local.strftime("%H:%M"), + }) + + active_kind = None + active_east_west_kind = None + active_start_utc = None + + if current_kind is not None: + active_kind = current_kind + active_east_west_kind = current_east_west_kind + active_start_utc = refine_transition(previous_time, current, current_kind, find_start=True) + + previous_time = current + previous_kind = current_kind + current += scan_step + + if active_kind is not None and active_start_utc is not None: + clamped_start_utc = max(active_start_utc, utc_start) + clamped_end_utc = utc_end + if clamped_end_utc > clamped_start_utc: + start_local = clamped_start_utc.astimezone(tz) + end_local = clamped_end_utc.astimezone(tz) + duration_minutes = max( + 1, + int(round((clamped_end_utc - clamped_start_utc).total_seconds() / 60.0)), + ) + side_text = direction_text_from_kinds(active_kind, active_east_west_kind) + events.append({ + "kind": active_kind, + "direction": active_east_west_kind, + "label": f"Alle 4 Jupitermonde {side_text} von Jupiter", + "duration_label": format_duration_label(duration_minutes), + "duration_minutes": duration_minutes, + "utc_iso": clamped_start_utc.isoformat().replace("+00:00", "Z"), + "local_iso": start_local.isoformat(), + "local_date": start_local.strftime("%d.%m.%Y"), + "local_time": start_local.strftime("%H:%M"), + "end_utc_iso": clamped_end_utc.isoformat().replace("+00:00", "Z"), + "end_local_iso": end_local.isoformat(), + "end_local_date": end_local.strftime("%d.%m.%Y"), + "end_local_time": end_local.strftime("%H:%M"), + }) + + events.sort(key=lambda item: item["local_iso"]) + + return { + "ok": True, + "action": "jupiter_moons_one_side_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( @@ -2815,7 +3135,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", "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", "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", "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", "moon_star_occultations_for_month"]}) action = sys.argv[1] args = sys.argv[2:] @@ -2915,12 +3235,17 @@ def main() -> None: print(json.dumps(result, ensure_ascii=False)) return + if action == "jupiter_moons_one_side_for_month": + result = action_jupiter_moons_one_side_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", "golden_handle_for_month", "season_changes_for_month", "time_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", "venus_peak_magnitude_for_month", "outer_planet_events_for_month", "outer_planet_stations_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", "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", "moon_star_occultations_for_month"]}) if __name__ == "__main__":