= '0' && $value <= '9') { return (int) $value; } $upper = strtoupper($value); if ($upper >= 'A' && $upper <= 'V') { return ord($upper) - ord('A') + 10; } return null; } function decodeMpcPackedDate(?string $packed): ?string { if ($packed === null || strlen($packed) !== 5) { return null; } $centuryMap = [ 'I' => 1800, 'J' => 1900, 'K' => 2000, ]; $centuryCode = strtoupper($packed[0]); if (!isset($centuryMap[$centuryCode])) { return null; } $yearPart = substr($packed, 1, 2); if (!ctype_digit($yearPart)) { return null; } $month = decodeMpcPackedDateChar($packed[3]); $day = decodeMpcPackedDateChar($packed[4]); if ($month === null || $day === null || $month < 1 || $month > 12 || $day < 1 || $day > 31) { return null; } return sprintf('%04d-%02d-%02d', $centuryMap[$centuryCode] + (int) $yearPart, $month, $day); } function buildCometPerihelionIso(array $comet): ?string { $year = isset($comet['year_of_perihelion']) ? (int) $comet['year_of_perihelion'] : 0; $month = isset($comet['month_of_perihelion']) ? (int) $comet['month_of_perihelion'] : 0; $dayValue = isset($comet['day_of_perihelion']) ? (float) $comet['day_of_perihelion'] : 0.0; if ($year <= 0 || $month < 1 || $month > 12 || $dayValue <= 0.0) { return null; } $day = (int) floor($dayValue); if ($day < 1 || $day > 31) { return null; } $fractionalDay = $dayValue - $day; $seconds = (int) round($fractionalDay * 86400.0); try { $date = new DateTimeImmutable(sprintf('%04d-%02d-%02d 00:00:00', $year, $month, $day), new DateTimeZone('UTC')); } catch (Throwable $e) { return null; } return $date->modify(sprintf('+%d seconds', $seconds))->format('c'); } if ($loggedIn && isset($_SESSION['user_id'])) { try { $cfg = require __DIR__ . '/../config/database.php'; $pdo = new PDO( sprintf('mysql:host=%s;dbname=%s;charset=%s', $cfg['host'], $cfg['dbname'], $cfg['charset'] ?? 'utf8mb4'), $cfg['user'], $cfg['pass'], [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ] ); $stmtLoc = $pdo->prepare( 'SELECT id, name, latitude, longitude, elevation, timezone FROM app_user_locations WHERE user_id = ? AND is_default = 1 LIMIT 1' ); $stmtLoc->execute([(int) $_SESSION['user_id']]); $defaultLocation = $stmtLoc->fetch() ?: null; $stmtFavoriteMinorplanets = $pdo->prepare( 'SELECT mp.id, mp.mp_number, mp.designation_text, mp.desig_packed, mp.epoch_packed, mp.mean_anomaly_deg, mp.arg_perihelion_deg, mp.ascending_node_deg, mp.inclination_deg, mp.eccentricity, mp.mean_motion_deg_per_day, mp.semimajor_axis_au FROM app_user_minorplanets aum INNER JOIN minorplanets_mpc mp ON mp.id = aum.minorplanet_id WHERE aum.user_id = ? ORDER BY aum.is_favorite DESC, mp.mp_number IS NULL ASC, mp.mp_number ASC, mp.designation_text ASC' ); $stmtFavoriteMinorplanets->execute([(int) $_SESSION['user_id']]); foreach ($stmtFavoriteMinorplanets->fetchAll() as $minorplanet) { $minorplanetLabel = trim((string) ($minorplanet['designation_text'] ?? '')); if ($minorplanetLabel === '') { $minorplanetLabel = trim((string) ($minorplanet['desig_packed'] ?? '')); } if ($minorplanetLabel === '') { continue; } if ( $minorplanet['mp_number'] !== null && !str_contains($minorplanetLabel, '(' . (string) $minorplanet['mp_number'] . ')') ) { $minorplanetLabel = '(' . (string) $minorplanet['mp_number'] . ') ' . $minorplanetLabel; } $bodyKey = 'minorplanet:' . (string) $minorplanet['id']; $favoriteMinorplanetOptions[$bodyKey] = $minorplanetLabel; $minorplanetPayloads[$bodyKey] = [ 'id' => (int) $minorplanet['id'], 'designation' => $minorplanetLabel, 'epochDateIso' => decodeMpcPackedDate((string) ($minorplanet['epoch_packed'] ?? '')), 'meanAnomalyDeg' => isset($minorplanet['mean_anomaly_deg']) ? (float) $minorplanet['mean_anomaly_deg'] : null, 'argPerihelionDeg' => isset($minorplanet['arg_perihelion_deg']) ? (float) $minorplanet['arg_perihelion_deg'] : null, 'ascendingNodeDeg' => isset($minorplanet['ascending_node_deg']) ? (float) $minorplanet['ascending_node_deg'] : null, 'inclinationDeg' => isset($minorplanet['inclination_deg']) ? (float) $minorplanet['inclination_deg'] : null, 'eccentricity' => isset($minorplanet['eccentricity']) ? (float) $minorplanet['eccentricity'] : null, 'meanMotionDegPerDay' => isset($minorplanet['mean_motion_deg_per_day']) ? (float) $minorplanet['mean_motion_deg_per_day'] : null, 'semimajorAxisAu' => isset($minorplanet['semimajor_axis_au']) ? (float) $minorplanet['semimajor_axis_au'] : null, ]; } $stmtFavoriteComets = $pdo->prepare( 'SELECT c.id, c.designation_and_name, c.year_of_perihelion, c.month_of_perihelion, c.day_of_perihelion, c.perihelion_dist_au, c.eccentricity, c.arg_perihelion_deg, c.ascending_node_deg, c.inclination_deg FROM app_user_comets auc INNER JOIN comets_mpc c ON c.id = auc.comet_id WHERE auc.user_id = ? ORDER BY auc.is_favorite DESC, c.designation_and_name ASC' ); $stmtFavoriteComets->execute([(int) $_SESSION['user_id']]); foreach ($stmtFavoriteComets->fetchAll() as $comet) { $cometLabel = trim((string) ($comet['designation_and_name'] ?? '')); if ($cometLabel === '') { continue; } $bodyKey = 'comet:' . (string) $comet['id']; $favoriteCometOptions[$bodyKey] = $cometLabel; $cometPayloads[$bodyKey] = [ 'id' => (int) $comet['id'], 'designation' => $cometLabel, 'perihelionIso' => buildCometPerihelionIso($comet), 'perihelionDistanceAu' => isset($comet['perihelion_dist_au']) ? (float) $comet['perihelion_dist_au'] : null, 'eccentricity' => isset($comet['eccentricity']) ? (float) $comet['eccentricity'] : null, 'argPerihelionDeg' => isset($comet['arg_perihelion_deg']) ? (float) $comet['arg_perihelion_deg'] : null, 'ascendingNodeDeg' => isset($comet['ascending_node_deg']) ? (float) $comet['ascending_node_deg'] : null, 'inclinationDeg' => isset($comet['inclination_deg']) ? (float) $comet['inclination_deg'] : null, ]; } } catch (Throwable $e) { $defaultLocation = null; $favoriteMinorplanetOptions = []; $favoriteCometOptions = []; $minorplanetPayloads = []; $cometPayloads = []; } } $planetOptions = [ 'Sun' => 'Sonne', 'Moon' => 'Mond', 'Mercury' => 'Merkur', 'Venus' => 'Venus', 'Mars' => 'Mars', 'Jupiter' => 'Jupiter', 'Saturn' => 'Saturn', 'Uranus' => 'Uranus', 'Neptune' => 'Neptun', ]; $bodyOptions = $planetOptions + $favoriteMinorplanetOptions + $favoriteCometOptions; $intervalOptions = [ '1' => '1 Minute', '10' => '10 Minuten', '30' => '30 Minuten', '60' => '1 Stunde', '360' => '6 Stunden', '720' => '12 Stunden', '1440' => '1 Tag', '10080' => '7 Tage', '20160' => '14 Tage', '43200' => '30 Tage', ]; $rangeUnitOptions = [ 'minutes' => 'Minuten', 'hours' => 'Stunden', 'days' => 'Tage', 'weeks' => 'Wochen', 'months' => 'Monate', 'years' => 'Jahre', ]; $selectedBody = (string) ($_POST['body'] ?? 'Mercury'); if (!isset($bodyOptions[$selectedBody])) { $selectedBody = 'Mercury'; } $selectedInterval = (string) ($_POST['interval_minutes'] ?? '1440'); if (!isset($intervalOptions[$selectedInterval])) { $selectedInterval = '1440'; } $selectedRangeUnit = (string) ($_POST['range_unit'] ?? 'days'); if (!isset($rangeUnitOptions[$selectedRangeUnit])) { $selectedRangeUnit = 'days'; } $rangeValueInput = trim((string) ($_POST['range_value'] ?? '7')); if ($rangeValueInput === '') { $rangeValueInput = '7'; } function ephemeriden_h(mixed $value): string { return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8'); } function ephemeriden_slug(string $value): string { $value = strtolower(trim($value)); $value = str_replace( ['ä', 'ö', 'ü', 'ß', ' '], ['ae', 'oe', 'ue', 'ss', '-'], $value ); $value = preg_replace('/[^a-z0-9\-]+/', '-', $value) ?? $value; $value = trim($value, '-'); return $value !== '' ? $value : 'ephemeriden'; } function runEphemeridenPythonApi(string $action, array $args): array { $scriptPath = __DIR__ . '/py/ephemeriden_api.py'; $pythonCandidates = PHP_OS_FAMILY === 'Windows' ? ['python', 'py'] : ['/usr/bin/python3', '/usr/bin/python', 'python3', 'python']; $escapedArguments = array_map('escapeshellarg', array_merge([$scriptPath, $action], $args)); $lastError = 'Python konnte nicht gestartet werden.'; foreach ($pythonCandidates as $pythonBinary) { $command = $pythonBinary . ' ' . implode(' ', $escapedArguments) . ' 2>&1'; $output = []; $resultCode = 0; exec($command, $output, $resultCode); $joined = trim(implode("\n", $output)); $decoded = json_decode($joined, true); if (is_array($decoded)) { return [ 'ok' => (bool) ($decoded['ok'] ?? false), 'data' => $decoded, 'raw' => $joined, 'command' => $command, ]; } if ($joined !== '') { $lastError = $joined; } elseif ($resultCode !== 0) { $lastError = 'Fehlercode ' . $resultCode . ' bei ' . $pythonBinary; } } return [ 'ok' => false, 'error' => $lastError, ]; } $pageLocation = null; if (is_array($defaultLocation) && $defaultLocation !== []) { $pageLocation = [ 'name' => trim((string) ($defaultLocation['name'] ?? '')), 'latitude' => isset($defaultLocation['latitude']) ? (float) $defaultLocation['latitude'] : null, 'longitude' => isset($defaultLocation['longitude']) ? (float) $defaultLocation['longitude'] : null, 'elevation' => isset($defaultLocation['elevation']) && $defaultLocation['elevation'] !== null ? (float) $defaultLocation['elevation'] : 0.0, 'timezone' => (string) ($defaultLocation['timezone'] ?? 'Europe/Berlin'), ]; } $defaultLocationName = $pageLocation['name'] ?? ''; $errorMessage = ''; $ephemerisRows = []; $exportFormat = (string) ($_POST['export_format'] ?? ''); if ($_SERVER['REQUEST_METHOD'] === 'POST') { if ($pageLocation === null || $pageLocation['latitude'] === null || $pageLocation['longitude'] === null) { $errorMessage = 'Kein gültiger Standard-Standort verfügbar. Bitte zuerst in den Einstellungen einen Standard-Standort setzen.'; } elseif (false && (str_starts_with($selectedBody, 'comet:') || str_starts_with($selectedBody, 'minorplanet:'))) { $errorMessage = 'Favoriten-Kometen und Favoriten-Kleinplaneten sind jetzt in der Auswahl sichtbar. Die eigentliche Ephemeriden-Berechnung dafür binden wir als Nächstes an.'; } elseif (!ctype_digit($rangeValueInput) || (int) $rangeValueInput < 1 || (int) $rangeValueInput > 31) { $errorMessage = 'Der Zeitraum muss als ganze Zahl zwischen 1 und 31 angegeben werden.'; } else { $pythonAction = 'planet_ephemeris'; $pythonArgs = [ (string) $pageLocation['latitude'], (string) $pageLocation['longitude'], (string) $pageLocation['elevation'], (string) $pageLocation['timezone'], $selectedBody, $selectedInterval, $rangeValueInput, $selectedRangeUnit, ]; if (isset($minorplanetPayloads[$selectedBody])) { $pythonAction = 'small_body_ephemeris'; $pythonArgs = [ (string) $pageLocation['latitude'], (string) $pageLocation['longitude'], (string) $pageLocation['elevation'], (string) $pageLocation['timezone'], 'minorplanet', base64_encode(json_encode($minorplanetPayloads[$selectedBody], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?: '{}'), $selectedInterval, $rangeValueInput, $selectedRangeUnit, ]; } elseif (isset($cometPayloads[$selectedBody])) { $pythonAction = 'small_body_ephemeris'; $pythonArgs = [ (string) $pageLocation['latitude'], (string) $pageLocation['longitude'], (string) $pageLocation['elevation'], (string) $pageLocation['timezone'], 'comet', base64_encode(json_encode($cometPayloads[$selectedBody], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?: '{}'), $selectedInterval, $rangeValueInput, $selectedRangeUnit, ]; } $pythonResult = runEphemeridenPythonApi($pythonAction, $pythonArgs); if (!$pythonResult['ok']) { $errorMessage = (string) ( $pythonResult['data']['error'] ?? $pythonResult['error'] ?? 'Die Ephemeriden konnten nicht berechnet werden.' ); } else { $ephemerisRows = $pythonResult['data']['rows'] ?? []; if (!is_array($ephemerisRows)) { $ephemerisRows = []; } if ($exportFormat === 'csv' && $ephemerisRows !== []) { $filename = 'ephemeriden-' . ephemeriden_slug((string) ($bodyOptions[$selectedBody] ?? 'objekt')) . '.csv'; header('Content-Type: text/csv; charset=utf-8'); header('Content-Disposition: attachment; filename="' . $filename . '"'); header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0'); echo "\xEF\xBB\xBF"; $handle = fopen('php://output', 'wb'); if ($handle === false) { exit; } fputcsv($handle, ['Name des Objekts', 'Datum', 'Uhrzeit', 'RA (J2000)', 'RA (J2000, dezimal h)', 'Dek (J2000)', 'Dek (J2000, dezimal °)', 'Aufgang', 'Untergang'], ';', '"', '\\'); foreach ($ephemerisRows as $row) { fputcsv($handle, [ (string) ($row['object_name'] ?? ''), (string) ($row['date_local'] ?? ''), (string) ($row['time_local'] ?? ''), (string) ($row['ra'] ?? ''), (string) ($row['ra_decimal_hours'] ?? ''), (string) ($row['dec'] ?? ''), (string) ($row['dec_decimal_deg'] ?? ''), (string) (($row['rise'] ?? null) ?? '—'), (string) (($row['set'] ?? null) ?? '—'), ], ';', '"', '\\'); } fclose($handle); exit; } } } } require __DIR__ . '/header.php'; ?>

Ephemeriden

Standard-Standort:
Name des Objekts Datum Uhrzeit RA (J2000) Dek (J2000) Aufgang Untergang
Für die gewählte Anfrage wurden keine Tabellenzeilen erzeugt.
Noch keine Berechnung ausgeführt.