'Januar', 2 => 'Februar', 3 => 'Maerz', 4 => 'April', 5 => 'Mai', 6 => 'Juni', 7 => 'Juli', 8 => 'August', 9 => 'September', 10 => 'Oktober', 11 => 'November', 12 => 'Dezember', ]; $monthSkyTimes = [ 1 => ['hour' => 22, 'minute' => 0], 2 => ['hour' => 22, 'minute' => 0], 3 => ['hour' => 22, 'minute' => 0], 4 => ['hour' => 23, 'minute' => 0], 5 => ['hour' => 23, 'minute' => 0], 6 => ['hour' => 23, 'minute' => 30], 7 => ['hour' => 23, 'minute' => 30], 8 => ['hour' => 23, 'minute' => 0], 9 => ['hour' => 23, 'minute' => 0], 10 => ['hour' => 23, 'minute' => 0], 11 => ['hour' => 22, 'minute' => 0], 12 => ['hour' => 22, 'minute' => 0], ]; $starData = []; $lineStarData = []; $constellationLines = []; $messierData = []; $planetData = []; $moonData = null; $moonPhaseEvents = []; $goldenHandleEvents = []; $moonPlanetApproaches = []; $moonDeepSkyApproaches = []; $planetBrightStarApproaches = []; $mercuryGoodVisibilityEvents = []; $planetParadeEvents = []; $planetConstellationChangeEvents = []; $planetConjunctions = []; $eclipseEvents = []; $moonApsisEvents = []; $sunApsisEvents = []; $innerPlanetElongationEvents = []; $venusPeakMagnitudeEvents = []; $outerPlanetEvents = []; $outerPlanetStationEvents = []; $jupiterMoonSideEvents = []; $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); } } if (!function_exists('buildObservationTipInsertSql')) { function buildObservationTipInsertSql(array $events, DateTimeZone $timezone): string { $rows = []; foreach ($events as $event) { if (!is_array($event)) { continue; } $tip = trim((string) ($event['event'] ?? '')); if ($tip === '') { continue; } $moment = null; $localIso = trim((string) ($event['local_iso'] ?? '')); if ($localIso !== '') { try { $moment = new DateTimeImmutable($localIso); } catch (Throwable $e) { $moment = null; } } if (!$moment instanceof DateTimeImmutable) { $date = trim((string) ($event['date'] ?? '')); $time = trim((string) ($event['time'] ?? '')); if ($date !== '' && $time !== '') { $moment = DateTimeImmutable::createFromFormat('d.m.Y H:i', $date . ' ' . $time, $timezone) ?: null; } } if (!$moment instanceof DateTimeImmutable) { continue; } $rows[] = sprintf( "('%s', '%s')", $moment->setTimezone($timezone)->format('Y-m-d H:i:s'), str_replace("'", "''", $tip) ); } if ($rows === []) { return ''; } return "INSERT INTO `astro_sonobs_observationtips` (`moment`, `tip`)\nVALUES\n " . implode(",\n ", $rows) . ";\n"; } } if (!function_exists('escapeIcalText')) { function escapeIcalText(string $value): string { return str_replace( ["\\", ";", ",", "\r\n", "\n", "\r"], ["\\\\", "\\;", "\\,", "\\n", "\\n", "\\n"], $value ); } } if (!function_exists('foldIcalLine')) { function foldIcalLine(string $line): string { $chunks = preg_split('/(.{1,73})/u', $line, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); if ($chunks === false || $chunks === []) { return $line; } $firstChunk = array_shift($chunks); return $firstChunk . ($chunks ? "\r\n " . implode("\r\n ", $chunks) : ''); } } if (!function_exists('buildObservationTipCalendarIcs')) { function buildObservationTipCalendarIcs(array $events, DateTimeZone $timezone): string { $lines = [ 'BEGIN:VCALENDAR', 'VERSION:2.0', 'PRODID:-//AstroTools//Monatsvorhersage//DE', 'CALSCALE:GREGORIAN', 'METHOD:PUBLISH', 'X-WR-CALNAME:AstroTools Monatsereignisse', ]; $createdUtc = (new DateTimeImmutable('now', new DateTimeZone('UTC')))->format('Ymd\THis\Z'); foreach ($events as $index => $event) { if (!is_array($event)) { continue; } $summary = trim((string) ($event['event'] ?? '')); if ($summary === '') { continue; } $moment = null; $localIso = trim((string) ($event['local_iso'] ?? '')); if ($localIso !== '') { try { $moment = new DateTimeImmutable($localIso); } catch (Throwable $e) { $moment = null; } } if (!$moment instanceof DateTimeImmutable) { $date = trim((string) ($event['date'] ?? '')); $time = trim((string) ($event['time'] ?? '')); if ($date !== '' && $time !== '') { $moment = DateTimeImmutable::createFromFormat('d.m.Y H:i', $date . ' ' . $time, $timezone) ?: null; } } if (!$moment instanceof DateTimeImmutable) { continue; } $startUtc = $moment->setTimezone(new DateTimeZone('UTC')); $endMoment = null; $endLocalIso = trim((string) ($event['end_local_iso'] ?? '')); if ($endLocalIso !== '') { try { $endMoment = new DateTimeImmutable($endLocalIso); } catch (Throwable $e) { $endMoment = null; } } if (!$endMoment instanceof DateTimeImmutable) { $durationMinutes = isset($event['duration_minutes']) ? max(1, (int) $event['duration_minutes']) : 15; $endMoment = $moment->modify('+' . $durationMinutes . ' minutes'); } $endUtc = $endMoment->setTimezone(new DateTimeZone('UTC')); $uidBase = sha1($startUtc->format(DateTimeInterface::ATOM) . '|' . $summary . '|' . (string) $index); $eventLines = [ 'BEGIN:VEVENT', 'UID:' . $uidBase . '@astrotools.local', 'DTSTAMP:' . $createdUtc, 'DTSTART:' . $startUtc->format('Ymd\THis\Z'), 'DTEND:' . $endUtc->format('Ymd\THis\Z'), 'SUMMARY:' . escapeIcalText($summary), 'DESCRIPTION:' . escapeIcalText($summary), 'END:VEVENT', ]; foreach ($eventLines as $eventLine) { $lines[] = foldIcalLine($eventLine); } } $lines[] = 'END:VCALENDAR'; return implode("\r\n", $lines) . "\r\n"; } } 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'; $chartLocationName = (string) (($currentLocation['name'] ?? null) ?: 'Sternwarte Sonneberg'); $chartLatitude = isset($currentLocation['latitude']) ? (float) $currentLocation['latitude'] : 50.3547; $chartLongitude = isset($currentLocation['longitude']) ? (float) $currentLocation['longitude'] : 11.1900; $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; $planetErrorText = null; $pythonArgs = [ $pythonScriptPath, 'month_sky_context', (string) $chartLatitude, (string) $chartLongitude, (string) $chartElevation, (string) $selectedYear, (string) $selectedMonth, (string) $monthTime['hour'], (string) $monthTime['minute'], $chartTimezone, ]; $pythonCommand = $pythonExecutable . ' ' . implode(' ', array_map('escapeshellarg', $pythonArgs)) . ' 2>&1'; exec($pythonCommand, $pythonOutput, $pythonExitCode); $pythonRaw = trim(implode("\n", $pythonOutput)); $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'] : []; } $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 = [ $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'] : []; } $moonDeepSkyOutput = []; $moonDeepSkyExitCode = 0; $moonDeepSkyArgs = [ $pythonScriptPath, 'moon_deep_sky_approaches_for_month', (string) $chartLatitude, (string) $chartLongitude, (string) $chartElevation, (string) $selectedYear, (string) $selectedMonth, $chartTimezone, (string) $moonDeepSkyMaxSeparationDeg, ]; $moonDeepSkyCommand = $pythonExecutable . ' ' . implode(' ', array_map('escapeshellarg', $moonDeepSkyArgs)) . ' 2>&1'; exec($moonDeepSkyCommand, $moonDeepSkyOutput, $moonDeepSkyExitCode); $moonDeepSkyRaw = trim(implode("\n", $moonDeepSkyOutput)); $moonDeepSkyDecoded = json_decode($moonDeepSkyRaw, true); if (is_array($moonDeepSkyDecoded) && ($moonDeepSkyDecoded['ok'] ?? false)) { $moonDeepSkyApproaches = is_array($moonDeepSkyDecoded['approaches'] ?? null) ? $moonDeepSkyDecoded['approaches'] : []; } $planetBrightStarOutput = []; $planetBrightStarExitCode = 0; $planetBrightStarArgs = [ $pythonScriptPath, 'planet_bright_star_approaches_for_month', (string) $chartLatitude, (string) $chartLongitude, (string) $chartElevation, (string) $selectedYear, (string) $selectedMonth, $chartTimezone, (string) $planetBrightStarMaxSeparationDeg, ]; $planetBrightStarCommand = $pythonExecutable . ' ' . implode(' ', array_map('escapeshellarg', $planetBrightStarArgs)) . ' 2>&1'; exec($planetBrightStarCommand, $planetBrightStarOutput, $planetBrightStarExitCode); $planetBrightStarRaw = trim(implode("\n", $planetBrightStarOutput)); $planetBrightStarDecoded = json_decode($planetBrightStarRaw, true); if (is_array($planetBrightStarDecoded) && ($planetBrightStarDecoded['ok'] ?? false)) { $planetBrightStarApproaches = is_array($planetBrightStarDecoded['approaches'] ?? null) ? $planetBrightStarDecoded['approaches'] : []; } $planetConjunctionOutput = []; $planetConjunctionExitCode = 0; $planetConjunctionArgs = [ $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'] : []; } $mercuryGoodVisibilityOutput = []; $mercuryGoodVisibilityExitCode = 0; $mercuryGoodVisibilityArgs = [ $pythonScriptPath, 'mercury_good_visibility_for_month', (string) $chartLatitude, (string) $chartLongitude, (string) $chartElevation, (string) $selectedYear, (string) $selectedMonth, $chartTimezone, (string) $mercuryGoodVisibilitySampleOffsetMinutes, (string) $mercuryGoodVisibilityMinAltitudeDeg, (string) $mercuryGoodVisibilityMaxSunAltitudeDeg, (string) $mercuryGoodVisibilityMinConsecutiveDays, ]; $mercuryGoodVisibilityCommand = $pythonExecutable . ' ' . implode(' ', array_map('escapeshellarg', $mercuryGoodVisibilityArgs)) . ' 2>&1'; exec($mercuryGoodVisibilityCommand, $mercuryGoodVisibilityOutput, $mercuryGoodVisibilityExitCode); $mercuryGoodVisibilityRaw = trim(implode("\n", $mercuryGoodVisibilityOutput)); $mercuryGoodVisibilityDecoded = json_decode($mercuryGoodVisibilityRaw, true); if (is_array($mercuryGoodVisibilityDecoded) && ($mercuryGoodVisibilityDecoded['ok'] ?? false)) { $mercuryGoodVisibilityEvents = is_array($mercuryGoodVisibilityDecoded['events'] ?? null) ? $mercuryGoodVisibilityDecoded['events'] : []; } $planetParadeOutput = []; $planetParadeExitCode = 0; $planetParadeArgs = [ $pythonScriptPath, 'planet_parades_for_month', (string) $chartLatitude, (string) $chartLongitude, (string) $chartElevation, (string) $selectedYear, (string) $selectedMonth, $chartTimezone, (string) $planetParadeSampleOffsetMinutes, (string) $planetParadeMinAltitudeDeg, (string) $planetParadeMaxSunAltitudeDeg, (string) $planetParadeMaxAzimuthSpanDeg, (string) $planetParadeMinPlanetCount, (string) $planetParadeMinConsecutiveDays, ]; $planetParadeCommand = $pythonExecutable . ' ' . implode(' ', array_map('escapeshellarg', $planetParadeArgs)) . ' 2>&1'; exec($planetParadeCommand, $planetParadeOutput, $planetParadeExitCode); $planetParadeRaw = trim(implode("\n", $planetParadeOutput)); $planetParadeDecoded = json_decode($planetParadeRaw, true); if (is_array($planetParadeDecoded) && ($planetParadeDecoded['ok'] ?? false)) { $planetParadeEvents = is_array($planetParadeDecoded['events'] ?? null) ? $planetParadeDecoded['events'] : []; } $planetConstellationChangeOutput = []; $planetConstellationChangeExitCode = 0; $planetConstellationChangeArgs = [ $pythonScriptPath, 'planet_constellation_changes_for_month', (string) $selectedYear, (string) $selectedMonth, $chartTimezone, ]; $planetConstellationChangeCommand = $pythonExecutable . ' ' . implode(' ', array_map('escapeshellarg', $planetConstellationChangeArgs)) . ' 2>&1'; exec($planetConstellationChangeCommand, $planetConstellationChangeOutput, $planetConstellationChangeExitCode); $planetConstellationChangeRaw = trim(implode("\n", $planetConstellationChangeOutput)); $planetConstellationChangeDecoded = json_decode($planetConstellationChangeRaw, true); if (is_array($planetConstellationChangeDecoded) && ($planetConstellationChangeDecoded['ok'] ?? false)) { $planetConstellationChangeEvents = is_array($planetConstellationChangeDecoded['events'] ?? null) ? $planetConstellationChangeDecoded['events'] : []; } $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); } } } } foreach ($moonPhaseEvents as $phaseEvent) { if (!is_array($phaseEvent)) { continue; } $monthEventList[] = [ 'event' => (string) ($phaseEvent['label'] ?? ''), 'date' => (string) ($phaseEvent['local_date'] ?? ''), 'time' => (string) ($phaseEvent['local_time'] ?? ''), 'local_iso' => (string) ($phaseEvent['local_iso'] ?? ''), 'type' => 'moon_phase', ]; } foreach ($goldenHandleEvents as $goldenHandleEvent) { if (!is_array($goldenHandleEvent)) { continue; } $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 ($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'] ?? ''), 'end_local_iso' => (string) ($jupiterMoonSideEvent['end_local_iso'] ?? ''), 'duration_minutes' => isset($jupiterMoonSideEvent['duration_minutes']) ? (int) $jupiterMoonSideEvent['duration_minutes'] : null, 'type' => 'jupiter_moons_one_side', ]; } 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 ($moonDeepSkyApproaches 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 Sternhaufen') . ($distanceText !== '' ? ' (' . $distanceText . ')' : '')), 'date' => (string) ($approachEvent['local_date'] ?? ''), 'time' => (string) ($approachEvent['local_time'] ?? ''), 'local_iso' => (string) ($approachEvent['local_iso'] ?? ''), 'type' => 'moon_deep_sky', ]; } foreach ($planetBrightStarApproaches as $approachEvent) { if (!is_array($approachEvent)) { continue; } $distanceText = isset($approachEvent['separation_deg']) ? number_format((float) ($approachEvent['separation_deg']), 2, ',', '') . ' Grad' : ''; $monthEventList[] = [ 'event' => trim((string) ($approachEvent['label'] ?? 'Planet nahe hellem Stern') . ($distanceText !== '' ? ' (' . $distanceText . ')' : '')), 'date' => (string) ($approachEvent['local_date'] ?? ''), 'time' => (string) ($approachEvent['local_time'] ?? ''), 'local_iso' => (string) ($approachEvent['local_iso'] ?? ''), 'type' => 'planet_bright_star', ]; } foreach ($planetConjunctions as $conjunctionEvent) { if (!is_array($conjunctionEvent)) { continue; } $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 ($mercuryGoodVisibilityEvents as $visibilityEvent) { if (!is_array($visibilityEvent)) { continue; } $monthEventList[] = [ 'event' => (string) ($visibilityEvent['label'] ?? 'Gute Merkur-Sichtbarkeit'), 'date' => (string) ($visibilityEvent['local_date'] ?? ''), 'time' => (string) ($visibilityEvent['local_time'] ?? ''), 'local_iso' => (string) ($visibilityEvent['local_iso'] ?? ''), 'end_local_iso' => (string) ($visibilityEvent['end_local_iso'] ?? ''), 'duration_minutes' => isset($visibilityEvent['duration_minutes']) ? (int) $visibilityEvent['duration_minutes'] : null, 'type' => 'mercury_good_visibility', ]; } foreach ($planetParadeEvents as $paradeEvent) { if (!is_array($paradeEvent)) { continue; } $monthEventList[] = [ 'event' => (string) ($paradeEvent['label'] ?? 'Planetenparade'), 'date' => (string) ($paradeEvent['local_date'] ?? ''), 'time' => (string) ($paradeEvent['local_time'] ?? ''), 'local_iso' => (string) ($paradeEvent['local_iso'] ?? ''), 'end_local_iso' => (string) ($paradeEvent['end_local_iso'] ?? ''), 'duration_minutes' => isset($paradeEvent['duration_minutes']) ? (int) $paradeEvent['duration_minutes'] : null, 'type' => 'planet_parade', ]; } foreach ($planetConstellationChangeEvents as $changeEvent) { if (!is_array($changeEvent)) { continue; } $monthEventList[] = [ 'event' => (string) ($changeEvent['label'] ?? 'Planet wechselt Sternbild'), 'date' => (string) ($changeEvent['local_date'] ?? ''), 'time' => (string) ($changeEvent['local_time'] ?? ''), 'local_iso' => (string) ($changeEvent['local_iso'] ?? ''), 'type' => 'planet_constellation_change', ]; } 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'] ?? '')) ); $observationTipInsertSql = buildObservationTipInsertSql($monthEventList, new DateTimeZone($chartTimezone)); $observationTipCalendarIcs = buildObservationTipCalendarIcs($monthEventList, new DateTimeZone($chartTimezone)); $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); $observationTipCalendarIcsJson = json_encode($observationTipCalendarIcs, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); $observationTipCalendarFilenameJson = json_encode(sprintf('astrotools-monatsereignisse-%04d-%02d.ics', $selectedYear, $selectedMonth), 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