Files
skyview.astronomiemuseum.de/public/comets.php
T
2026-04-11 14:59:20 +02:00

512 lines
18 KiB
PHP

<?php
declare(strict_types=1);
ini_set('display_errors', '1');
error_reporting(E_ALL);
session_start();
$loggedIn = isset($_SESSION['user_id']);
$pageTitle = 'Kometen - AstroTools';
$headerIntroPre = 'Himmel & Planeten';
$headerIntroTitle = 'Kometen';
$headerIntroSub = 'Interessante Kometen aus der MPC-Datenbank mit Fokus auf bevorstehende und aktuelle Periheldurchgaenge.';
function h(?string $value): string
{
return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
}
function formatDecimalGerman(?float $value, int $decimals = 3): string
{
if ($value === null || !is_finite($value)) {
return '-';
}
return number_format($value, $decimals, ',', '.');
}
function buildPerihelionDate(array $row): ?DateTimeImmutable
{
$year = isset($row['year_of_perihelion']) ? (int) $row['year_of_perihelion'] : 0;
$month = isset($row['month_of_perihelion']) ? (int) $row['month_of_perihelion'] : 0;
$dayValue = isset($row['day_of_perihelion']) ? (float) $row['day_of_perihelion'] : 0.0;
if ($year === 0 || $month < 1 || $month > 12 || $dayValue <= 0) {
return null;
}
$day = (int) floor($dayValue);
if ($day < 1 || $day > 31) {
return null;
}
$fractionalDay = $dayValue - $day;
$seconds = (int) round($fractionalDay * 86400);
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));
}
function classifyOrbitType(string $orbitType): string
{
return match (strtoupper(trim($orbitType))) {
'P' => 'Periodisch',
'C' => 'Nicht periodisch',
'I' => 'Interstellar',
'D' => 'Verschollen / zerfallen',
'A' => 'Asteroidal',
default => trim($orbitType) !== '' ? trim($orbitType) : '-',
};
}
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',
$config['host'],
$config['dbname'],
$config['charset'] ?? 'utf8mb4'
);
$pdo = new PDO(
$dsn,
$config['user'],
$config['pass'],
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]
);
$userId = isset($_SESSION['user_id']) ? (int) $_SESSION['user_id'] : 0;
$today = new DateTimeImmutable('today', new DateTimeZone('UTC'));
$windowStart = $today->modify('-180 days');
$windowEnd = $today->modify('+365 days');
$queryYearMin = (int) $windowStart->format('Y') - 1;
$queryYearMax = (int) $windowEnd->format('Y') + 1;
$stmtComets = $pdo->prepare("
SELECT
c.*,
CASE WHEN auc.`comet_id` IS NULL THEN 0 ELSE 1 END AS `is_tracked`,
CASE WHEN auc.`is_favorite` = 1 THEN 1 ELSE 0 END AS `is_favorite`
FROM `comets_mpc` c
LEFT JOIN `app_user_comets` auc
ON auc.`comet_id` = c.`id`
AND auc.`user_id` = :user_id
WHERE c.`year_of_perihelion` BETWEEN :year_min AND :year_max
OR auc.`comet_id` IS NOT NULL
ORDER BY c.`year_of_perihelion` ASC, c.`month_of_perihelion` ASC, c.`day_of_perihelion` ASC, c.`designation_and_name` ASC
");
$stmtComets->execute([
':user_id' => $userId,
':year_min' => $queryYearMin,
':year_max' => $queryYearMax,
]);
$allComets = [];
$trackedComets = [];
$interestingComets = [];
foreach ($stmtComets 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['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
? 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)
: '-';
$allComets[] = $row;
if ((int) ($row['is_tracked'] ?? 0) === 1) {
$trackedComets[] = $row;
}
if ($perihelionDate !== null && $perihelionDate >= $windowStart && $perihelionDate <= $windowEnd) {
$interestingComets[] = $row;
}
}
usort($trackedComets, static function (array $left, array $right): int {
$favoriteCompare = ((int) $right['is_favorite']) <=> ((int) $left['is_favorite']);
if ($favoriteCompare !== 0) {
return $favoriteCompare;
}
return ((int) ($left['days_from_today'] ?? PHP_INT_MAX)) <=> ((int) ($right['days_from_today'] ?? PHP_INT_MAX));
});
usort($interestingComets, static function (array $left, array $right): int {
$leftDistance = abs((int) ($left['days_from_today'] ?? PHP_INT_MAX));
$rightDistance = abs((int) ($right['days_from_today'] ?? PHP_INT_MAX));
if ($leftDistance !== $rightDistance) {
return $leftDistance <=> $rightDistance;
}
return strcmp((string) $left['designation_and_name'], (string) $right['designation_and_name']);
});
$interestingComets = array_slice($interestingComets, 0, 24);
$orbitTypeCounts = [];
foreach ($allComets as $comet) {
$orbitKey = (string) ($comet['orbit_type'] ?? '?');
$orbitTypeCounts[$orbitKey] = ($orbitTypeCounts[$orbitKey] ?? 0) + 1;
}
arsort($orbitTypeCounts);
?>
<?php require __DIR__ . '/header.php'; ?>
<style>
.comets-layout {
display: grid;
gap: 1.25rem;
}
.comets-intro {
color: #d9e7ff;
line-height: 1.65;
margin: 0;
}
.comets-summary {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 0.9rem;
}
.comets-stat {
padding: 1rem 1.1rem;
border-radius: 1rem;
border: 1px solid rgba(130, 170, 255, 0.16);
background:
radial-gradient(circle at top left, rgba(82, 153, 255, 0.12), transparent 36%),
linear-gradient(180deg, rgba(13, 19, 33, 0.96), rgba(8, 11, 20, 0.98));
}
.comets-stat-label {
color: #9db2d4;
font-size: 0.82rem;
text-transform: uppercase;
letter-spacing: 0.08em;
}
.comets-stat-value {
margin-top: 0.45rem;
color: #ffffff;
font-size: 1.8rem;
font-weight: 700;
}
.comets-stat-help {
margin-top: 0.35rem;
color: #c7d6f4;
font-size: 0.92rem;
}
.comets-card {
padding: 1.2rem;
}
.comets-card h2 {
margin-bottom: 0.45rem;
}
.comets-card .hint {
margin-top: 0;
}
.comets-chip-row {
display: flex;
flex-wrap: wrap;
gap: 0.7rem;
margin-top: 1rem;
}
.comets-chip {
padding: 0.45rem 0.7rem;
border-radius: 999px;
border: 1px solid rgba(130, 170, 255, 0.18);
background: rgba(255, 255, 255, 0.04);
color: #dce8ff;
font-size: 0.92rem;
}
.comets-table-wrap {
overflow-x: auto;
}
.comets-table {
width: 100%;
border-collapse: collapse;
margin-top: 1rem;
}
.comets-table th,
.comets-table td {
padding: 0.78rem 0.8rem;
border-bottom: 1px solid rgba(130, 170, 255, 0.12);
text-align: left;
vertical-align: top;
}
.comets-table th {
color: #a9bee0;
font-size: 0.84rem;
text-transform: uppercase;
letter-spacing: 0.08em;
}
.comets-table td {
color: #eef4ff;
}
.comets-name {
font-weight: 600;
}
.comets-meta {
display: flex;
flex-wrap: wrap;
gap: 0.45rem;
margin-top: 0.35rem;
}
.comets-badge {
display: inline-flex;
align-items: center;
gap: 0.35rem;
padding: 0.22rem 0.5rem;
border-radius: 999px;
font-size: 0.8rem;
color: #eef4ff;
background: rgba(255, 255, 255, 0.08);
border: 1px solid rgba(255, 255, 255, 0.12);
}
.comets-badge.is-favorite {
background: rgba(255, 208, 102, 0.12);
border-color: rgba(255, 208, 102, 0.28);
color: #ffd98b;
}
.comets-badge.is-tracked {
background: rgba(130, 170, 255, 0.12);
border-color: rgba(130, 170, 255, 0.26);
color: #bfd6ff;
}
.comets-days {
font-weight: 600;
}
.comets-days.is-past {
color: #ffb2a8;
}
.comets-days.is-upcoming {
color: #b8f0c8;
}
@media (max-width: 960px) {
.comets-summary {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (max-width: 640px) {
.comets-summary {
grid-template-columns: 1fr;
}
}
</style>
<div class="comets-layout">
<section class="card comets-card">
<p class="comets-intro">
Diese erste Version liest die vorhandenen Kometen direkt aus <code>comets_mpc</code> und hebt Objekte hervor,
deren Perihel zwischen <strong><?= h($windowStart->format('d.m.Y')) ?></strong> und
<strong><?= h($windowEnd->format('d.m.Y')) ?></strong> liegt.
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.
</p>
<div class="comets-chip-row">
<?php foreach (array_slice($orbitTypeCounts, 0, 5, true) as $orbitType => $count): ?>
<span class="comets-chip"><?= h(classifyOrbitType((string) $orbitType)) ?>: <?= (int) $count ?></span>
<?php endforeach; ?>
</div>
</section>
<section class="comets-summary">
<div class="comets-stat">
<div class="comets-stat-label">Kometen im Blickfenster</div>
<div class="comets-stat-value"><?= count($interestingComets) ?></div>
<div class="comets-stat-help">Perihel im aktuellen Zeitfenster</div>
</div>
<div class="comets-stat">
<div class="comets-stat-label">Meine Kometen</div>
<div class="comets-stat-value"><?= count($trackedComets) ?></div>
<div class="comets-stat-help">Aus den Favoriten uebernommen</div>
</div>
<div class="comets-stat">
<div class="comets-stat-label">Gesamt in Datenbank</div>
<div class="comets-stat-value"><?= count($allComets) ?></div>
<div class="comets-stat-help">Geladene Datensaetze im relevanten Jahresbereich</div>
</div>
<div class="comets-stat">
<div class="comets-stat-label">Zeitraum</div>
<div class="comets-stat-value"><?= h($today->format('Y')) ?></div>
<div class="comets-stat-help"><?= h($windowStart->format('d.m.Y')) ?> bis <?= h($windowEnd->format('d.m.Y')) ?></div>
</div>
</section>
<section class="card comets-card">
<h2>Meine Kometen</h2>
<?php if (empty($trackedComets)): ?>
<p class="hint">Du hast aktuell noch keine Kometen in deinen Favoriten. In <a href="my_favorites.php">Favoriten</a> kannst du welche markieren.</p>
<?php else: ?>
<div class="comets-table-wrap">
<table class="comets-table">
<thead>
<tr>
<th>Komet</th>
<th>Typ</th>
<th>Perihel</th>
<th>Abstand</th>
<th title="Sehr grobe Helligkeitsschaetzung nahe dem Perihel.">m grob</th>
</tr>
</thead>
<tbody>
<?php foreach ($trackedComets as $comet): ?>
<?php
$daysClass = ((int) ($comet['days_from_today'] ?? 0)) < 0 ? 'is-past' : 'is-upcoming';
$daysLabel = $comet['days_from_today'] === null
? '-'
: ((int) $comet['days_from_today'] === 0
? 'Heute'
: (((int) $comet['days_from_today'] > 0 ? '+' : '') . (int) $comet['days_from_today'] . ' Tage'));
?>
<tr>
<td>
<div class="comets-name"><?= h((string) $comet['designation_and_name']) ?></div>
<div class="comets-meta">
<?php if ((int) $comet['is_favorite'] === 1): ?>
<span class="comets-badge is-favorite">Favorit</span>
<?php endif; ?>
<span class="comets-badge is-tracked">Beobachtet</span>
<?php if (!empty($comet['reference_text'])): ?>
<span class="comets-badge"><?= h((string) $comet['reference_text']) ?></span>
<?php endif; ?>
</div>
</td>
<td><?= h((string) $comet['orbit_type_label']) ?></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>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</section>
<section class="card comets-card">
<h2>Interessante Kometen</h2>
<p class="hint">Sortiert nach Naehe zum heutigen Datum bezogen auf den Periheldurchgang.</p>
<?php if (empty($interestingComets)): ?>
<p class="hint">Im aktuellen Zeitfenster wurden keine Kometen gefunden.</p>
<?php else: ?>
<div class="comets-table-wrap">
<table class="comets-table">
<thead>
<tr>
<th>Komet</th>
<th>Typ</th>
<th>Perihel</th>
<th>Abstand</th>
<th title="Sehr grobe Helligkeitsschaetzung nahe dem Perihel.">m grob</th>
<th>H</th>
<th>G</th>
</tr>
</thead>
<tbody>
<?php foreach ($interestingComets as $comet): ?>
<?php
$daysClass = ((int) ($comet['days_from_today'] ?? 0)) < 0 ? 'is-past' : 'is-upcoming';
$daysLabel = $comet['days_from_today'] === null
? '-'
: ((int) $comet['days_from_today'] === 0
? 'Heute'
: (((int) $comet['days_from_today'] > 0 ? '+' : '') . (int) $comet['days_from_today'] . ' Tage'));
?>
<tr>
<td>
<div class="comets-name"><?= h((string) $comet['designation_and_name']) ?></div>
<div class="comets-meta">
<?php if ((int) $comet['is_favorite'] === 1): ?>
<span class="comets-badge is-favorite">Favorit</span>
<?php elseif ((int) $comet['is_tracked'] === 1): ?>
<span class="comets-badge is-tracked">Beobachtet</span>
<?php endif; ?>
<?php if (!empty($comet['reference_text'])): ?>
<span class="comets-badge"><?= h((string) $comet['reference_text']) ?></span>
<?php endif; ?>
</div>
</td>
<td><?= h((string) $comet['orbit_type_label']) ?></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>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</section>
</div>
<?php require __DIR__ . '/footer.php'; ?>