index sonne und Mond rein und Sat Überflüge

This commit is contained in:
Eskimue
2026-04-02 08:45:41 +02:00
parent a3209f6131
commit c85d6a89b8
5 changed files with 470 additions and 73 deletions
+296 -67
View File
@@ -1,58 +1,236 @@
<?php
declare(strict_types=1);
ini_set('display_errors', '1');
error_reporting(E_ALL);
session_start();
$loggedIn = isset($_SESSION['user_id']);
$defaultLocation = null;
if ($loggedIn) {
$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 = (int)$_SESSION['user_id'];
$displayName = $_SESSION['display_name'] ?? '';
$username = $_SESSION['username'] ?? '';
$role = $_SESSION['role'] ?? 'user';
$stmtLocation = $pdo->prepare("
SELECT
`name`,
`latitude`,
`longitude`,
`elevation`,
`timezone`
FROM `app_user_locations`
WHERE `user_id` = :user_id
AND `is_default` = 1
LIMIT 1
");
$stmtLocation->execute([':user_id' => $userId]);
$defaultLocation = $stmtLocation->fetch();
if (!function_exists('h')) {
function h(?string $value): string { return htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8'); }
}
if (!function_exists('risesetTime')) {
function risesetTime(array $ev, string $key): string { return (!empty($ev[$key]['found'])) ? htmlspecialchars($ev[$key]['local_time'], ENT_QUOTES, 'UTF-8') : '—'; }
}
if (!function_exists('fmtLocalTime')) {
function fmtLocalTime(string $utcIso, string $tz): string { return (new DateTime($utcIso))->setTimezone(new DateTimeZone($tz))->format('H:i'); }
}
if (!function_exists('azDir')) {
function azDir(float $az): string { $dirs = ['N','NO','O','SO','S','SW','W','NW']; return $dirs[(int)round($az / 45) % 8]; }
}
function h(?string $value): string
function idx_tleChecksum(string $line): int
{
return htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
$sum = 0;
for ($i = 0; $i < 68; $i++) {
$c = $line[$i];
if (ctype_digit($c)) $sum += (int)$c;
elseif ($c === '-') $sum += 1;
}
return $sum % 10;
}
function idx_tleExp(float $val): string
{
if (abs($val) < 1e-99) return ' 00000-0';
$sign = $val >= 0 ? '+' : '-';
$abs = abs($val);
$exp = (int)floor(log10($abs)) + 1;
$mantissa = (int)round($abs / pow(10, $exp) * 1e5);
if ($mantissa >= 100000) { $mantissa = 10000; $exp++; }
return sprintf('%s%05d%s%d', $sign, $mantissa, $exp >= 0 ? '+' : '-', abs($exp));
}
function idx_tleMmDot(float $val): string
{
return sprintf('%s.%08d', $val >= 0 ? ' ' : '-', (int)round(abs($val) * 1e8));
}
function idx_tleEpoch(string $epochStr): string
{
$dt = new DateTimeImmutable($epochStr, new DateTimeZone('UTC'));
$yy = (int)$dt->format('Y') % 100;
$jan1 = new DateTimeImmutable($dt->format('Y') . '-01-01 00:00:00', new DateTimeZone('UTC'));
$day = ((float)$dt->format('U') - (float)$jan1->format('U')) / 86400.0 + 1.0;
return sprintf('%02d%012.8f', $yy, $day);
}
function idx_tleIntlDesig(?string $id): string
{
if (!$id) return str_repeat(' ', 8);
if (preg_match('/^(\d{4})-(\d+)([A-Z]*)$/i', trim($id), $m)) {
$yy = substr($m[1], 2, 2);
return substr(str_pad($yy . str_pad($m[2], 3, '0', STR_PAD_LEFT) . str_pad(strtoupper($m[3]), 3), 8), 0, 8);
}
return str_pad(substr($id, 0, 8), 8);
}
function buildTleLine1(array $sat, array $tle): string
{
$line = sprintf('1 %05d%1s %-8s %14s %10s %8s %8s %1d %4d',
(int)$sat['norad_cat_id'],
in_array($sat['classification_type'] ?? '', ['U','C','S'], true) ? $sat['classification_type'] : 'U',
idx_tleIntlDesig($sat['object_id'] ?? null),
idx_tleEpoch((string)$tle['epoch']),
idx_tleMmDot((float)($tle['mean_motion_dot'] ?? 0)),
idx_tleExp((float)($tle['mean_motion_ddot'] ?? 0)),
idx_tleExp((float)($tle['bstar'] ?? 0)),
(int)($tle['ephemeris_type'] ?? 0),
(int)($tle['element_set_no'] ?? 0)
);
$line = str_pad($line, 68);
return $line . idx_tleChecksum($line);
}
function buildTleLine2(array $sat, array $tle): string
{
$ecc = (float)($tle['eccentricity'] ?? 0);
$line = sprintf('2 %05d %8.4f %8.4f %7s %8.4f %8.4f %11.8f%5d',
(int)$sat['norad_cat_id'],
(float)($tle['inclination'] ?? 0),
(float)($tle['ra_of_asc_node'] ?? 0),
substr(sprintf('%.7f', $ecc), 2),
(float)($tle['arg_of_pericenter'] ?? 0),
(float)($tle['mean_anomaly'] ?? 0),
(float)($tle['mean_motion'] ?? 0),
(int)($tle['rev_at_epoch'] ?? 0)
);
$line = str_pad($line, 68);
return $line . idx_tleChecksum($line);
}
// Standort und astronomische Daten nur wenn eingeloggt und Standort vorhanden
$skyData = null;
$planetData = null;
$satData = null;
$pyError = null;
if ($loggedIn) {
// header.php lädt $currentLocation – wir brauchen ihn vorab
$config = require __DIR__ . '/../config/database.php';
$pdo = new PDO(
sprintf('mysql:host=%s;dbname=%s;charset=%s', $config['host'], $config['dbname'], $config['charset'] ?? 'utf8mb4'),
$config['user'],
$config['pass'],
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC]
);
$stmtLoc = $pdo->prepare(
'SELECT 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;
if ($defaultLocation) {
$lat = (float)$defaultLocation['latitude'];
$lon = (float)$defaultLocation['longitude'];
$elev = (float)($defaultLocation['elevation'] ?? 0);
$tz = $defaultLocation['timezone'] ?? 'Europe/Berlin';
$date = date('Y-m-d');
$scriptPath = realpath(__DIR__ . '/py/api.py');
if (file_exists('/usr/bin/python3')) {
$pythonBin = '/usr/bin/python3';
} elseif (file_exists('/usr/bin/python')) {
$pythonBin = '/usr/bin/python';
} else {
$pythonBin = 'C:\\Users\\HP\\AppData\\Local\\Python\\bin\\python.exe';
}
putenv('PYTHONUTF8=1');
putenv('PYTHONIOENCODING=utf-8');
// Sonne + Mond
$args = [
$pythonBin,
escapeshellarg($scriptPath),
escapeshellarg('sun_moon_rise_set'),
escapeshellarg((string)$lat),
escapeshellarg((string)$lon),
escapeshellarg((string)$elev),
escapeshellarg($date),
escapeshellarg($tz),
];
$cmd = implode(' ', $args) . ' 2>&1';
$out = [];
exec($cmd, $out);
$raw = json_decode(implode('', $out), true);
if (is_array($raw) && ($raw['ok'] ?? false)) {
$skyData = $raw;
} else {
$pyError = $raw['error'] ?? 'Fehler beim Berechnen der Aufgangszeiten.';
}
// Planeten
$args2 = [
$pythonBin,
escapeshellarg($scriptPath),
escapeshellarg('planet_rise_set'),
escapeshellarg((string)$lat),
escapeshellarg((string)$lon),
escapeshellarg((string)$elev),
escapeshellarg($date),
escapeshellarg($tz),
];
$cmd2 = implode(' ', $args2) . ' 2>&1';
$out2 = [];
exec($cmd2, $out2);
$raw2 = json_decode(implode('', $out2), true);
if (is_array($raw2) && ($raw2['ok'] ?? false)) {
$planetData = $raw2;
} elseif ($pyError === null) {
$pyError = $raw2['error'] ?? 'Fehler beim Berechnen der Planetenzeiten.';
}
// Satelliten-Überflüge heute — Favorit zuerst, dann alle gemerkten
$stmtSat = $pdo->prepare(
'SELECT s.id, s.norad_cat_id, s.object_name, s.object_type,
s.object_id, s.classification_type,
t.epoch, t.mean_motion, t.eccentricity, t.inclination,
t.ra_of_asc_node, t.arg_of_pericenter, t.mean_anomaly,
t.ephemeris_type, t.element_set_no, t.rev_at_epoch,
t.bstar, t.mean_motion_dot, t.mean_motion_ddot,
us.is_favorite
FROM app_user_satellites us
JOIN sat_satellites s ON s.id = us.satellite_id
JOIN sat_tle_current t ON t.satellite_id = s.id
WHERE us.user_id = ?
AND t.epoch IS NOT NULL
ORDER BY us.is_favorite DESC, s.object_name ASC
LIMIT 3'
);
$stmtSat->execute([(int)$_SESSION['user_id']]);
$userSats = $stmtSat->fetchAll();
if ($userSats) {
$tzObj = new DateTimeZone($tz);
$dayStart = new DateTime('today', $tzObj);
$dayStart->setTimezone(new DateTimeZone('UTC'));
$dayEnd = new DateTime('tomorrow', $tzObj);
$dayEnd->setTimezone(new DateTimeZone('UTC'));
$satsJson = json_encode(array_map(fn($r) => [
'id' => $r['id'],
'norad_cat_id' => $r['norad_cat_id'],
'object_name' => $r['object_name'],
'tle_line1' => buildTleLine1($r, $r),
'tle_line2' => buildTleLine2($r, $r),
'is_favorite' => (bool)$r['is_favorite'],
], $userSats), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
$args3 = [
$pythonBin,
escapeshellarg($scriptPath),
escapeshellarg('satellite_passes'),
escapeshellarg((string)$lat),
escapeshellarg((string)$lon),
escapeshellarg((string)$elev),
escapeshellarg($tz),
escapeshellarg($dayStart->format('Y-m-d\TH:i:s\Z')),
escapeshellarg($dayEnd->format('Y-m-d\TH:i:s\Z')),
escapeshellarg($satsJson),
];
$cmd3 = implode(' ', $args3) . ' 2>&1';
$out3 = [];
exec($cmd3, $out3);
$raw3 = json_decode(implode('', $out3), true);
if (is_array($raw3) && ($raw3['ok'] ?? false)) {
$satData = $raw3;
}
}
}
}
?>
<?php require __DIR__ . '/header.php'; ?>
@@ -70,36 +248,87 @@ function h(?string $value): string
<?php else: ?>
<p class="welcome-text">
Willkommen zurück, <strong><?= h($displayName !== '' ? $displayName : $username) ?></strong>.
</p>
<?php if (!$defaultLocation): ?>
<p class="hint">Noch kein Standort hinterlegt. <a href="settings.php">Jetzt anlegen</a></p>
<?php elseif ($pyError): ?>
<ul class="msg-error"><li><?= h($pyError) ?></li></ul>
<?php else: ?>
<div class="grid-2">
<p class="hint rise-set-location">
Aufgangs- und Untergangszeiten für
<strong><?= h($defaultLocation['name']) ?></strong>
&mdash; <?= h(date('d.m.Y')) ?>
</p>
<div class="card">
<h2>Konto</h2>
<table class="data-table">
<tr><td>Benutzername</td><td><?= h($username) ?></td></tr>
<tr><td>Rolle</td><td><?= h($role) ?></td></tr>
</table>
<?php $ev = $skyData['events'] ?? []; ?>
<div class="card riseset-card">
<h2>Heute</h2>
<div class="riseset-grid">
<span class="riseset-label riseset-main">☀ Sonne</span>
<span class="riseset-val riseset-main">↑ <?= risesetTime($ev, 'sunrise') ?></span>
<span class="riseset-val riseset-main">↓ <?= risesetTime($ev, 'sunset') ?></span>
<span class="riseset-label riseset-dim">Bürgerl.</span>
<span class="riseset-val riseset-dim"><?= risesetTime($ev, 'civil_dawn') ?></span>
<span class="riseset-val riseset-dim"><?= risesetTime($ev, 'civil_dusk') ?></span>
<span class="riseset-label riseset-dim">Nautisch</span>
<span class="riseset-val riseset-dim"><?= risesetTime($ev, 'nautical_dawn') ?></span>
<span class="riseset-val riseset-dim"><?= risesetTime($ev, 'nautical_dusk') ?></span>
<span class="riseset-label riseset-dim">Astronom.</span>
<span class="riseset-val riseset-dim"><?= risesetTime($ev, 'astro_dawn') ?></span>
<span class="riseset-val riseset-dim"><?= risesetTime($ev, 'astro_dusk') ?></span>
<span class="riseset-sep"></span><span class="riseset-sep"></span><span class="riseset-sep"></span>
<span class="riseset-label riseset-moon">☽ Mond</span>
<span class="riseset-val riseset-moon">↑ <?= risesetTime($ev, 'moonrise') ?></span>
<span class="riseset-val riseset-moon">↓ <?= risesetTime($ev, 'moonset') ?></span>
<span class="riseset-sep"></span><span class="riseset-sep"></span><span class="riseset-sep"></span>
<?php foreach (($planetData['planets'] ?? []) as $planet): ?>
<span class="riseset-label"><?= h($planet['name']) ?></span>
<span class="riseset-val">↑ <?= ($planet['rise']['found'] ?? false) ? h($planet['rise']['local_time']) : '—' ?></span>
<span class="riseset-val">↓ <?= ($planet['set']['found'] ?? false) ? h($planet['set']['local_time']) : '—' ?></span>
<?php endforeach; ?>
</div>
</div>
<div class="card">
<h2>Standard-Standort</h2>
<?php if ($defaultLocation): ?>
<table class="data-table">
<tr><td>Name</td><td><?= h($defaultLocation['name']) ?></td></tr>
<tr><td>Breitengrad</td><td><?= h((string)$defaultLocation['latitude']) ?></td></tr>
<tr><td>Längengrad</td><td><?= h((string)$defaultLocation['longitude']) ?></td></tr>
<tr><td>Höhe</td><td><?= h($defaultLocation['elevation'] !== null ? (string)$defaultLocation['elevation'] . ' m' : '—') ?></td></tr>
<tr><td>Zeitzone</td><td><?= h($defaultLocation['timezone']) ?></td></tr>
</table>
<?php if ($satData): ?>
<?php
$favName = '';
$favPasses = [];
foreach (($satData['results'] ?? []) as $sat) {
if ($sat['is_favorite']) {
$favName = $sat['name'];
$favPasses = $sat['passes'] ?? [];
break;
}
}
usort($favPasses, fn($a, $b) => strcmp($a['aos_utc'], $b['aos_utc']));
?>
<div class="card riseset-card">
<h2>☆ <?= h($favName ?: 'Favorit') ?></h2>
<?php if (empty($favPasses)): ?>
<p class="hint">Heute keine Überflüge.</p>
<?php else: ?>
<p class="hint">Noch kein Standort hinterlegt. <a href="settings.php">Jetzt anlegen</a></p>
<div class="riseset-grid sat-passes-grid">
<span class="riseset-label riseset-dim">AOS</span>
<span class="riseset-label riseset-dim">LOS</span>
<span class="riseset-label riseset-dim">Max</span>
<?php foreach ($favPasses as $p): ?>
<span class="riseset-val riseset-main"><?= fmtLocalTime($p['aos_utc'], $tz) ?> <small><?= $p['aos_az'] !== null ? azDir((float)$p['aos_az']) : '' ?></small></span>
<span class="riseset-val"><?= fmtLocalTime($p['los_utc'], $tz) ?> <small><?= $p['los_az'] !== null ? azDir((float)$p['los_az']) : '' ?></small></span>
<span class="riseset-val riseset-dim"><?= round((float)$p['peak_el']) ?>°</span>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
<?php endif; ?>
</div>
<?php endif; ?>
<?php endif; ?>