Kometenseite erstmal fertig

This commit is contained in:
Eskimue
2026-04-11 16:00:27 +02:00
parent f7cd34aeb1
commit 8f0bc4b688
7 changed files with 677 additions and 43 deletions
+235 -38
View File
@@ -16,6 +16,81 @@ function h(?string $value): string
return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
}
function runPythonApi(string $action, array $args): array
{
$scriptPath = __DIR__ . '/py/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,
];
}
function runPythonApiWithJsonPayloadFile(string $action, array $payload): array
{
$tempFile = tempnam(sys_get_temp_dir(), 'skyview_py_');
if ($tempFile === false) {
return [
'ok' => false,
'error' => 'Temporäre Datei für Python-Payload konnte nicht erstellt werden.',
];
}
$jsonPayload = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
if ($jsonPayload === false) {
@unlink($tempFile);
return [
'ok' => false,
'error' => 'Python-Payload konnte nicht als JSON kodiert werden.',
];
}
if (file_put_contents($tempFile, $jsonPayload) === false) {
@unlink($tempFile);
return [
'ok' => false,
'error' => 'Temporäre Datei für Python-Payload konnte nicht geschrieben werden.',
];
}
try {
return runPythonApi($action, ['@' . $tempFile]);
} finally {
@unlink($tempFile);
}
}
function formatDecimalGerman(?float $value, int $decimals = 3): string
{
if ($value === null || !is_finite($value)) {
@@ -25,6 +100,51 @@ function formatDecimalGerman(?float $value, int $decimals = 3): string
return number_format($value, $decimals, ',', '.');
}
function formatRaHours(?float $hours): string
{
if ($hours === null || !is_finite($hours)) {
return '-';
}
$normalized = fmod($hours, 24.0);
if ($normalized < 0.0) {
$normalized += 24.0;
}
$totalSeconds = (int) round($normalized * 3600.0);
$raHours = intdiv($totalSeconds, 3600) % 24;
$raMinutes = intdiv($totalSeconds % 3600, 60);
$raSeconds = $totalSeconds % 60;
return sprintf('%02dh %02dm %02ds', $raHours, $raMinutes, $raSeconds);
}
function formatDeclination(?float $degrees): string
{
if ($degrees === null || !is_finite($degrees)) {
return '-';
}
$sign = $degrees < 0.0 ? '-' : '+';
$absolute = abs($degrees);
$deg = (int) floor($absolute);
$minutesFloat = ($absolute - $deg) * 60.0;
$minutes = (int) floor($minutesFloat);
$seconds = (int) round(($minutesFloat - $minutes) * 60.0);
if ($seconds >= 60) {
$seconds -= 60;
$minutes += 1;
}
if ($minutes >= 60) {
$minutes -= 60;
$deg += 1;
}
return sprintf('%s%02d° %02d\' %02d"', $sign, $deg, $minutes, $seconds);
}
function buildPerihelionDate(array $row): ?DateTimeImmutable
{
$year = isset($row['year_of_perihelion']) ? (int) $row['year_of_perihelion'] : 0;
@@ -64,20 +184,6 @@ function classifyOrbitType(string $orbitType): string
};
}
function estimatePerihelMagnitude(?float $absoluteMagnitudeH, ?float $perihelionDistanceAu): ?float
{
if ($absoluteMagnitudeH === null || !is_finite($absoluteMagnitudeH) || $perihelionDistanceAu === null || !is_finite($perihelionDistanceAu) || $perihelionDistanceAu <= 0.0) {
return null;
}
$heliocentricDistance = max(0.05, $perihelionDistanceAu);
$geocentricDistance = max(0.2, abs(1.0 - $perihelionDistanceAu));
return $absoluteMagnitudeH
+ (5.0 * log10($geocentricDistance))
+ (10.0 * log10($heliocentricDistance));
}
$config = require __DIR__ . '/../config/database.php';
$dsn = sprintf(
'mysql:host=%s;dbname=%s;charset=%s',
@@ -121,36 +227,29 @@ $stmtComets->execute([
':year_min' => $queryYearMin,
':year_max' => $queryYearMax,
]);
$cometRows = $stmtComets->fetchAll();
$allComets = [];
$trackedComets = [];
$interestingComets = [];
foreach ($stmtComets as $row) {
foreach ($cometRows as $row) {
$perihelionDate = buildPerihelionDate($row);
$daysFromToday = $perihelionDate ? (int) floor(($perihelionDate->getTimestamp() - $today->getTimestamp()) / 86400) : null;
$row['perihelion_date_object'] = $perihelionDate;
$row['perihelion_date_display'] = $perihelionDate ? $perihelionDate->setTimezone(new DateTimeZone('Europe/Berlin'))->format('d.m.Y H:i') . ' UTC' : '-';
$row['perihelion_date_display'] = $perihelionDate ? $perihelionDate->format('d.m.Y H:i') . ' UTC' : '-';
$row['days_from_today'] = $daysFromToday;
$row['orbit_type_label'] = classifyOrbitType((string) ($row['orbit_type'] ?? ''));
$row['absolute_magnitude_display'] = isset($row['absolute_magnitude_h']) && $row['absolute_magnitude_h'] !== null
? formatDecimalGerman((float) $row['absolute_magnitude_h'], 1)
: '-';
$row['slope_parameter_display'] = isset($row['slope_parameter_g']) && $row['slope_parameter_g'] !== null
$row['photometric_index_display'] = isset($row['slope_parameter_g']) && $row['slope_parameter_g'] !== null
? formatDecimalGerman((float) $row['slope_parameter_g'], 1)
: '-';
$row['perihelion_dist_display'] = formatDecimalGerman(isset($row['perihelion_dist_au']) ? (float) $row['perihelion_dist_au'] : null, 3);
$row['eccentricity_display'] = formatDecimalGerman(isset($row['eccentricity']) ? (float) $row['eccentricity'] : null, 4);
$row['inclination_display'] = formatDecimalGerman(isset($row['inclination_deg']) ? (float) $row['inclination_deg'] : null, 2);
$estimatedPerihelMagnitude = estimatePerihelMagnitude(
isset($row['absolute_magnitude_h']) && $row['absolute_magnitude_h'] !== null ? (float) $row['absolute_magnitude_h'] : null,
isset($row['perihelion_dist_au']) && $row['perihelion_dist_au'] !== null ? (float) $row['perihelion_dist_au'] : null
);
$row['estimated_perihel_magnitude'] = $estimatedPerihelMagnitude;
$row['estimated_perihel_magnitude_display'] = $estimatedPerihelMagnitude !== null
? formatDecimalGerman($estimatedPerihelMagnitude, 1)
: '-';
$row['estimated_magnitude'] = null;
$row['estimated_magnitude_display'] = '-';
$row['brightness_error'] = null;
$allComets[] = $row;
@@ -183,6 +282,94 @@ usort($interestingComets, static function (array $left, array $right): int {
});
$interestingComets = array_slice($interestingComets, 0, 24);
$visibleCometIds = [];
foreach (array_merge($trackedComets, $interestingComets) as $visibleComet) {
$visibleId = isset($visibleComet['id']) ? (int) $visibleComet['id'] : 0;
if ($visibleId > 0) {
$visibleCometIds[$visibleId] = true;
}
}
$calculationMoment = new DateTimeImmutable('now', new DateTimeZone('UTC'));
$brightnessByCometId = [];
$brightnessError = null;
if ($visibleCometIds !== []) {
$pythonPayload = [
'date' => $calculationMoment->format('Y-m-d\TH:i:s\Z'),
'comets' => array_values(array_map(static function (array $row): array {
return [
'id' => $row['id'] ?? null,
'year_of_perihelion' => $row['year_of_perihelion'] ?? null,
'month_of_perihelion' => $row['month_of_perihelion'] ?? null,
'day_of_perihelion' => $row['day_of_perihelion'] ?? null,
'perihelion_dist_au' => $row['perihelion_dist_au'] ?? null,
'eccentricity' => $row['eccentricity'] ?? null,
'arg_perihelion_deg' => $row['arg_perihelion_deg'] ?? null,
'ascending_node_deg' => $row['ascending_node_deg'] ?? null,
'inclination_deg' => $row['inclination_deg'] ?? null,
'absolute_magnitude_h' => $row['absolute_magnitude_h'] ?? null,
'slope_parameter_g' => $row['slope_parameter_g'] ?? null,
];
}, array_filter($cometRows, static function (array $row) use ($visibleCometIds): bool {
$rowId = isset($row['id']) ? (int) $row['id'] : 0;
return $rowId > 0 && isset($visibleCometIds[$rowId]);
}))),
];
$pythonResult = runPythonApiWithJsonPayloadFile('comet_brightnesses', $pythonPayload);
if (($pythonResult['ok'] ?? false) && isset($pythonResult['data']['results']) && is_array($pythonResult['data']['results'])) {
foreach ($pythonResult['data']['results'] as $result) {
$resultId = isset($result['id']) ? (int) $result['id'] : 0;
if ($resultId > 0) {
$brightnessByCometId[$resultId] = $result;
}
}
} else {
$brightnessError = (string) ($pythonResult['error'] ?? 'Python-Berechnung nicht verfuegbar.');
}
}
$applyBrightness = static function (array $row) use ($brightnessByCometId): array {
$brightnessData = isset($row['id']) ? ($brightnessByCometId[(int) $row['id']] ?? null) : null;
$estimatedMagnitude = isset($brightnessData['estimated_magnitude']) && is_numeric($brightnessData['estimated_magnitude'])
? (float) $brightnessData['estimated_magnitude']
: null;
$raHours = isset($brightnessData['ra_hours']) && is_numeric($brightnessData['ra_hours'])
? (float) $brightnessData['ra_hours']
: null;
$decDeg = isset($brightnessData['dec_deg']) && is_numeric($brightnessData['dec_deg'])
? (float) $brightnessData['dec_deg']
: null;
$heliocentricDistance = isset($brightnessData['heliocentric_distance_au']) && is_numeric($brightnessData['heliocentric_distance_au'])
? (float) $brightnessData['heliocentric_distance_au']
: null;
$geocentricDistance = isset($brightnessData['geocentric_distance_au']) && is_numeric($brightnessData['geocentric_distance_au'])
? (float) $brightnessData['geocentric_distance_au']
: null;
$row['estimated_magnitude'] = $estimatedMagnitude;
$row['estimated_magnitude_display'] = $estimatedMagnitude !== null
? formatDecimalGerman($estimatedMagnitude, 1)
: '-';
$row['ra_hours'] = $raHours;
$row['ra_display'] = formatRaHours($raHours);
$row['dec_deg'] = $decDeg;
$row['dec_display'] = formatDeclination($decDeg);
$row['heliocentric_distance_au'] = $heliocentricDistance;
$row['heliocentric_distance_display'] = formatDecimalGerman($heliocentricDistance, 3);
$row['geocentric_distance_au'] = $geocentricDistance;
$row['geocentric_distance_display'] = formatDecimalGerman($geocentricDistance, 3);
$row['brightness_error'] = is_array($brightnessData) && !empty($brightnessData['error'])
? (string) $brightnessData['error']
: null;
return $row;
};
$trackedComets = array_map($applyBrightness, $trackedComets);
$interestingComets = array_map($applyBrightness, $interestingComets);
$orbitTypeCounts = [];
foreach ($allComets as $comet) {
$orbitKey = (string) ($comet['orbit_type'] ?? '?');
@@ -365,9 +552,15 @@ arsort($orbitTypeCounts);
Nutzer-Favoriten aus <code>app_user_comets</code> werden zusaetzlich separat gezeigt.
</p>
<p class="hint">
Die Spalte <strong>m grob</strong> ist eine sehr einfache Helligkeitsschaetzung nahe dem Perihel
auf Basis von MPC-<code>H</code> und <code>q</code>. Sie ist nur als Orientierung gedacht.
Die Spalte <strong>m grob</strong> nutzt jetzt die uebliche MPC-Kometenformel fuer <strong>heute</strong>:
<code>H + 5 log(Delta) + 2,5 * n * log(r)</code>. Sie bleibt trotzdem nur eine Orientierung.
</p>
<p class="hint">
Position und Helligkeit werden fuer <strong><?= h($calculationMoment->format('d.m.Y H:i')) ?> UTC</strong> berechnet.
</p>
<?php if ($brightnessError !== null): ?>
<p class="hint">Die Python-Berechnung der Helligkeit war nicht verfuegbar: <?= h($brightnessError) ?></p>
<?php endif; ?>
<div class="comets-chip-row">
<?php foreach (array_slice($orbitTypeCounts, 0, 5, true) as $orbitType => $count): ?>
@@ -410,9 +603,11 @@ arsort($orbitTypeCounts);
<tr>
<th>Komet</th>
<th>Typ</th>
<th>RA</th>
<th>Dec</th>
<th>Perihel</th>
<th>Abstand</th>
<th title="Sehr grobe Helligkeitsschaetzung nahe dem Perihel.">m grob</th>
<th title="Grobe Helligkeitsschaetzung fuer heute nach H + 5 log(Delta) + 2,5 * n * log(r).">m grob</th>
</tr>
</thead>
<tbody>
@@ -439,9 +634,11 @@ arsort($orbitTypeCounts);
</div>
</td>
<td><?= h((string) $comet['orbit_type_label']) ?></td>
<td><?= h((string) $comet['ra_display']) ?></td>
<td><?= h((string) $comet['dec_display']) ?></td>
<td><?= h((string) $comet['perihelion_date_display']) ?></td>
<td><span class="comets-days <?= $daysClass ?>"><?= h($daysLabel) ?></span></td>
<td title="Sehr grobe Helligkeitsschaetzung nahe dem Perihel."><?= h((string) $comet['estimated_perihel_magnitude_display']) ?></td>
<td title="r = <?= h((string) $comet['heliocentric_distance_display']) ?> AE, Δ = <?= h((string) $comet['geocentric_distance_display']) ?> AE"><?= h((string) $comet['estimated_magnitude_display']) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
@@ -462,11 +659,11 @@ arsort($orbitTypeCounts);
<tr>
<th>Komet</th>
<th>Typ</th>
<th>RA</th>
<th>Dec</th>
<th>Perihel</th>
<th>Abstand</th>
<th title="Sehr grobe Helligkeitsschaetzung nahe dem Perihel.">m grob</th>
<th>H</th>
<th>G</th>
<th title="Grobe Helligkeitsschaetzung fuer heute nach H + 5 log(Delta) + 2,5 * n * log(r).">m grob</th>
</tr>
</thead>
<tbody>
@@ -494,11 +691,11 @@ arsort($orbitTypeCounts);
</div>
</td>
<td><?= h((string) $comet['orbit_type_label']) ?></td>
<td><?= h((string) $comet['ra_display']) ?></td>
<td><?= h((string) $comet['dec_display']) ?></td>
<td><?= h((string) $comet['perihelion_date_display']) ?></td>
<td><span class="comets-days <?= $daysClass ?>"><?= h($daysLabel) ?></span></td>
<td title="Sehr grobe Helligkeitsschaetzung nahe dem Perihel."><?= h((string) $comet['estimated_perihel_magnitude_display']) ?></td>
<td><?= h((string) $comet['absolute_magnitude_display']) ?></td>
<td><?= h((string) $comet['slope_parameter_display']) ?></td>
<td title="r = <?= h((string) $comet['heliocentric_distance_display']) ?> AE, Δ = <?= h((string) $comet['geocentric_distance_display']) ?> AE"><?= h((string) $comet['estimated_magnitude_display']) ?></td>
</tr>
<?php endforeach; ?>
</tbody>