Kleinigkeiten und Monatsvoschrau angefangen
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -76,6 +76,7 @@ $menuGroups = [
|
||||
|
||||
$adminLinks = [
|
||||
['href' => 'admin_users.php', 'label' => 'Benutzerverwaltung'],
|
||||
['href' => 'monatsvorhersage.php', 'label' => 'Monatsvorhersage'],
|
||||
];
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
|
||||
+34
-17
@@ -97,6 +97,7 @@ $skyData = null;
|
||||
$planetData = null;
|
||||
$satData = null;
|
||||
$pyError = null;
|
||||
$satError = null;
|
||||
|
||||
if ($loggedIn) {
|
||||
// header.php lädt $currentLocation – wir brauchen ihn vorab
|
||||
@@ -120,15 +121,17 @@ if ($loggedIn) {
|
||||
$lon = (float)$defaultLocation['longitude'];
|
||||
$elev = (float)($defaultLocation['elevation'] ?? 0);
|
||||
$tz = $defaultLocation['timezone'] ?? 'Europe/Berlin';
|
||||
$date = date('Y-m-d');
|
||||
$date = (new DateTimeImmutable('now', new DateTimeZone($tz)))->format('Y-m-d');
|
||||
|
||||
$scriptPath = realpath(__DIR__ . '/py/api.py');
|
||||
if (file_exists('/usr/bin/python3')) {
|
||||
if (PHP_OS_FAMILY === 'Windows') {
|
||||
$pythonBin = 'python';
|
||||
} elseif (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';
|
||||
$pythonBin = 'python';
|
||||
}
|
||||
putenv('PYTHONUTF8=1');
|
||||
putenv('PYTHONIOENCODING=utf-8');
|
||||
@@ -146,12 +149,14 @@ if ($loggedIn) {
|
||||
];
|
||||
$cmd = implode(' ', $args) . ' 2>&1';
|
||||
$out = [];
|
||||
exec($cmd, $out);
|
||||
$raw = json_decode(implode('', $out), true);
|
||||
$resultCode = 0;
|
||||
exec($cmd, $out, $resultCode);
|
||||
$rawText = trim(implode("\n", $out));
|
||||
$raw = json_decode($rawText, true);
|
||||
if (is_array($raw) && ($raw['ok'] ?? false)) {
|
||||
$skyData = $raw;
|
||||
} else {
|
||||
$pyError = $raw['error'] ?? 'Fehler beim Berechnen der Aufgangszeiten.';
|
||||
$pyError = $raw['error'] ?? ($rawText !== '' ? $rawText : 'Fehler beim Berechnen der Aufgangszeiten.');
|
||||
}
|
||||
|
||||
// Planeten
|
||||
@@ -167,12 +172,14 @@ if ($loggedIn) {
|
||||
];
|
||||
$cmd2 = implode(' ', $args2) . ' 2>&1';
|
||||
$out2 = [];
|
||||
exec($cmd2, $out2);
|
||||
$raw2 = json_decode(implode('', $out2), true);
|
||||
$resultCode2 = 0;
|
||||
exec($cmd2, $out2, $resultCode2);
|
||||
$rawText2 = trim(implode("\n", $out2));
|
||||
$raw2 = json_decode($rawText2, true);
|
||||
if (is_array($raw2) && ($raw2['ok'] ?? false)) {
|
||||
$planetData = $raw2;
|
||||
} elseif ($pyError === null) {
|
||||
$pyError = $raw2['error'] ?? 'Fehler beim Berechnen der Planetenzeiten.';
|
||||
$pyError = $raw2['error'] ?? ($rawText2 !== '' ? $rawText2 : 'Fehler beim Berechnen der Planetenzeiten.');
|
||||
}
|
||||
|
||||
// Satelliten-Überflüge heute — Favorit zuerst, dann alle gemerkten
|
||||
@@ -209,6 +216,7 @@ if ($loggedIn) {
|
||||
'tle_line2' => buildTleLine2($r, $r),
|
||||
'is_favorite' => (bool)$r['is_favorite'],
|
||||
], $userSats), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
||||
$satsPayload = base64_encode((string) $satsJson);
|
||||
|
||||
$args3 = [
|
||||
$pythonBin,
|
||||
@@ -220,14 +228,18 @@ if ($loggedIn) {
|
||||
escapeshellarg($tz),
|
||||
escapeshellarg($dayStart->format('Y-m-d\TH:i:s\Z')),
|
||||
escapeshellarg($dayEnd->format('Y-m-d\TH:i:s\Z')),
|
||||
escapeshellarg($satsJson),
|
||||
escapeshellarg($satsPayload),
|
||||
];
|
||||
$cmd3 = implode(' ', $args3) . ' 2>&1';
|
||||
$out3 = [];
|
||||
exec($cmd3, $out3);
|
||||
$raw3 = json_decode(implode('', $out3), true);
|
||||
$resultCode3 = 0;
|
||||
exec($cmd3, $out3, $resultCode3);
|
||||
$rawText3 = trim(implode("\n", $out3));
|
||||
$raw3 = json_decode($rawText3, true);
|
||||
if (is_array($raw3) && ($raw3['ok'] ?? false)) {
|
||||
$satData = $raw3;
|
||||
} else {
|
||||
$satError = $raw3['error'] ?? ($rawText3 !== '' ? $rawText3 : 'Fehler beim Berechnen der Satellitenüberflüge.');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -250,14 +262,14 @@ if ($loggedIn) {
|
||||
|
||||
<?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: ?>
|
||||
<?php elseif ($pyError): ?>
|
||||
<ul class="msg-error"><li><?= h($pyError) ?></li></ul>
|
||||
<?php else: ?>
|
||||
|
||||
<p class="hint rise-set-location">
|
||||
Aufgangs- und Untergangszeiten für
|
||||
<strong><?= h($defaultLocation['name']) ?></strong>
|
||||
— <?= h(date('d.m.Y')) ?>
|
||||
— <?= h((new DateTimeImmutable('now', new DateTimeZone($tz)))->format('d.m.Y')) ?>
|
||||
</p>
|
||||
|
||||
<?php $ev = $skyData['events'] ?? []; ?>
|
||||
@@ -296,7 +308,12 @@ if ($loggedIn) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ($satData): ?>
|
||||
<?php if ($satError): ?>
|
||||
<div class="card riseset-card">
|
||||
<h2>☆ Satellit</h2>
|
||||
<p class="hint"><?= h($satError) ?></p>
|
||||
</div>
|
||||
<?php elseif ($satData): ?>
|
||||
<?php
|
||||
$favName = '';
|
||||
$favPasses = [];
|
||||
|
||||
@@ -0,0 +1,392 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
session_start();
|
||||
|
||||
$loggedIn = isset($_SESSION['user_id']);
|
||||
$pageTitle = 'Monatsvorhersage - AstroTools';
|
||||
$bodyClass = 'admin-page monatsvorhersage-page';
|
||||
$publicBasePath = './';
|
||||
|
||||
$currentYear = (int) date('Y');
|
||||
$selectedYear = isset($_GET['year']) ? (int) $_GET['year'] : $currentYear;
|
||||
$selectedMonth = isset($_GET['month']) ? max(1, min(12, (int) $_GET['month'])) : (int) date('n');
|
||||
|
||||
$years = range($currentYear - 2, $currentYear + 5);
|
||||
$months = [
|
||||
1 => 'Januar',
|
||||
2 => 'Februar',
|
||||
3 => 'März',
|
||||
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],
|
||||
];
|
||||
$selectedSkyTime = $monthSkyTimes[$selectedMonth] ?? ['hour' => 22, 'minute' => 0];
|
||||
|
||||
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');
|
||||
|
||||
$pythonExecutable = PHP_OS_FAMILY === 'Windows' ? 'python' : '/usr/bin/python';
|
||||
$pythonScriptPath = __DIR__ . '/py/api.py';
|
||||
$pythonOutput = [];
|
||||
$pythonExitCode = 0;
|
||||
$pythonPayload = null;
|
||||
$pythonError = null;
|
||||
|
||||
$pythonArgs = [
|
||||
$pythonScriptPath,
|
||||
'month_sky_context',
|
||||
(string) $chartLatitude,
|
||||
(string) $chartLongitude,
|
||||
(string) $chartElevation,
|
||||
(string) $selectedYear,
|
||||
(string) $selectedMonth,
|
||||
(string) ((int) $selectedSkyTime['hour']),
|
||||
(string) ((int) $selectedSkyTime['minute']),
|
||||
$chartTimezone,
|
||||
];
|
||||
|
||||
$command = $pythonExecutable . ' ' . implode(' ', array_map('escapeshellarg', $pythonArgs)) . ' 2>&1';
|
||||
exec($command, $pythonOutput, $pythonExitCode);
|
||||
|
||||
$pythonRaw = trim(implode("\n", $pythonOutput));
|
||||
if ($pythonExitCode !== 0) {
|
||||
$pythonError = $pythonRaw !== '' ? $pythonRaw : 'Python-Aufruf fehlgeschlagen.';
|
||||
} else {
|
||||
$decoded = json_decode($pythonRaw, true);
|
||||
if (!is_array($decoded)) {
|
||||
$pythonError = $pythonRaw !== '' ? $pythonRaw : 'Python lieferte kein gültiges JSON.';
|
||||
} elseif (!($decoded['ok'] ?? false)) {
|
||||
$pythonError = (string) ($decoded['error'] ?? 'Python meldete einen Fehler.');
|
||||
} else {
|
||||
$pythonPayload = $decoded;
|
||||
}
|
||||
}
|
||||
|
||||
$bodyRows = [];
|
||||
if (is_array($pythonPayload['bodies'] ?? null)) {
|
||||
$bodyRows[] = $pythonPayload['bodies']['sun'] ?? null;
|
||||
$bodyRows[] = $pythonPayload['bodies']['moon'] ?? null;
|
||||
foreach (($pythonPayload['bodies']['planets'] ?? []) as $planet) {
|
||||
$bodyRows[] = $planet;
|
||||
}
|
||||
$bodyRows = array_values(array_filter($bodyRows, static fn ($row) => is_array($row)));
|
||||
}
|
||||
|
||||
$formatNumber = static function ($value, int $decimals = 1): string {
|
||||
if (!is_numeric($value)) {
|
||||
return '–';
|
||||
}
|
||||
return number_format((float) $value, $decimals, ',', '');
|
||||
};
|
||||
?>
|
||||
|
||||
<style>
|
||||
.monatsvorhersage-shell {
|
||||
max-width: 1180px;
|
||||
margin: 0 auto;
|
||||
display: grid;
|
||||
gap: 18px;
|
||||
}
|
||||
|
||||
.monatsvorhersage-panel {
|
||||
background: rgba(7, 12, 22, 0.78);
|
||||
border: 1px solid rgba(132, 155, 196, 0.18);
|
||||
border-radius: 18px;
|
||||
padding: 22px 24px;
|
||||
box-shadow: 0 18px 48px rgba(0, 0, 0, 0.22);
|
||||
}
|
||||
|
||||
.monatsvorhersage-panel h2,
|
||||
.monatsvorhersage-panel h3 {
|
||||
margin: 0 0 10px;
|
||||
}
|
||||
|
||||
.monatsvorhersage-panel p {
|
||||
margin: 0;
|
||||
color: rgba(228, 235, 247, 0.82);
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.monatsvorhersage-form {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 14px;
|
||||
align-items: end;
|
||||
}
|
||||
|
||||
.monatsvorhersage-field {
|
||||
min-width: 180px;
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.monatsvorhersage-field label {
|
||||
font-size: 0.92rem;
|
||||
color: rgba(228, 235, 247, 0.76);
|
||||
}
|
||||
|
||||
.monatsvorhersage-field select,
|
||||
.monatsvorhersage-form button,
|
||||
.monatsvorhersage-link {
|
||||
min-height: 44px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid rgba(132, 155, 196, 0.26);
|
||||
background: rgba(12, 20, 34, 0.92);
|
||||
color: #edf3ff;
|
||||
padding: 0 14px;
|
||||
font: inherit;
|
||||
text-decoration: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.monatsvorhersage-form button,
|
||||
.monatsvorhersage-link {
|
||||
background: linear-gradient(180deg, rgba(61, 98, 168, 0.95), rgba(40, 69, 122, 0.95));
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.monatsvorhersage-meta {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.monatsvorhersage-stat {
|
||||
border-radius: 14px;
|
||||
padding: 16px 18px;
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
border: 1px solid rgba(132, 155, 196, 0.14);
|
||||
}
|
||||
|
||||
.monatsvorhersage-stat strong {
|
||||
display: block;
|
||||
margin-bottom: 6px;
|
||||
color: #f4f7ff;
|
||||
}
|
||||
|
||||
.monatsvorhersage-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.monatsvorhersage-alert {
|
||||
padding: 14px 16px;
|
||||
border-radius: 14px;
|
||||
border: 1px solid rgba(255, 154, 154, 0.28);
|
||||
background: rgba(74, 18, 22, 0.52);
|
||||
color: #ffd7d7;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.monatsvorhersage-table-wrap {
|
||||
overflow-x: auto;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.monatsvorhersage-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
min-width: 760px;
|
||||
}
|
||||
|
||||
.monatsvorhersage-table th,
|
||||
.monatsvorhersage-table td {
|
||||
padding: 11px 10px;
|
||||
border-bottom: 1px solid rgba(132, 155, 196, 0.12);
|
||||
text-align: left;
|
||||
color: rgba(232, 238, 249, 0.9);
|
||||
font-size: 0.96rem;
|
||||
}
|
||||
|
||||
.monatsvorhersage-table th {
|
||||
color: rgba(199, 214, 240, 0.8);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.monatsvorhersage-pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 84px;
|
||||
padding: 4px 10px;
|
||||
border-radius: 999px;
|
||||
font-size: 0.84rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.monatsvorhersage-pill--up {
|
||||
background: rgba(87, 188, 138, 0.18);
|
||||
color: #bff3cf;
|
||||
}
|
||||
|
||||
.monatsvorhersage-pill--down {
|
||||
background: rgba(133, 152, 184, 0.16);
|
||||
color: #d5dfef;
|
||||
}
|
||||
|
||||
.monatsvorhersage-list {
|
||||
margin: 0;
|
||||
padding-left: 18px;
|
||||
color: rgba(228, 235, 247, 0.84);
|
||||
line-height: 1.65;
|
||||
}
|
||||
|
||||
code.monatsvorhersage-code {
|
||||
font-size: 0.92rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
<main class="page-content">
|
||||
<div class="monatsvorhersage-shell">
|
||||
<section class="monatsvorhersage-panel">
|
||||
<form class="monatsvorhersage-form" method="get">
|
||||
<div class="monatsvorhersage-field">
|
||||
<label for="mv-year">Jahr</label>
|
||||
<select id="mv-year" name="year">
|
||||
<?php foreach ($years as $year): ?>
|
||||
<option value="<?= (int) $year ?>"<?= $year === $selectedYear ? ' selected' : '' ?>><?= (int) $year ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="monatsvorhersage-field">
|
||||
<label for="mv-month">Monat</label>
|
||||
<select id="mv-month" name="month">
|
||||
<?php foreach ($months as $monthNumber => $monthLabel): ?>
|
||||
<option value="<?= (int) $monthNumber ?>"<?= $monthNumber === $selectedMonth ? ' selected' : '' ?>><?= htmlspecialchars($monthLabel, ENT_QUOTES, 'UTF-8') ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<button type="submit">Monat laden</button>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section class="monatsvorhersage-panel">
|
||||
<h2>Serverseitige Monatsbasis</h2>
|
||||
<p>Diese Vorschau läuft jetzt ohne Browser-Rechenlogik über die Python-AstronomyEngine. Damit ist der erste Baustein für den späteren Cronjob vorhanden: Datum, Standort und Himmelskoordinaten kommen bereits serverseitig aus <code class="monatsvorhersage-code">public/py/api.py</code>.</p>
|
||||
<div class="monatsvorhersage-meta" style="margin-top:16px;">
|
||||
<div class="monatsvorhersage-stat">
|
||||
<strong>Ausgewählter Monat</strong>
|
||||
<?= htmlspecialchars($months[$selectedMonth] . ' ' . (string) $selectedYear, ENT_QUOTES, 'UTF-8') ?>
|
||||
</div>
|
||||
<div class="monatsvorhersage-stat">
|
||||
<strong>Ziel-Uhrzeit</strong>
|
||||
<?= sprintf('%02d:%02d Uhr', (int) $selectedSkyTime['hour'], (int) $selectedSkyTime['minute']) ?>
|
||||
</div>
|
||||
<div class="monatsvorhersage-stat">
|
||||
<strong>Standort</strong>
|
||||
<?= htmlspecialchars($chartLocationName, ENT_QUOTES, 'UTF-8') ?><br>
|
||||
<?= $formatNumber($chartLatitude, 4) ?>°, <?= $formatNumber($chartLongitude, 4) ?>°
|
||||
</div>
|
||||
<div class="monatsvorhersage-stat">
|
||||
<strong>Zeitzone</strong>
|
||||
<?= htmlspecialchars($chartTimezone, ENT_QUOTES, 'UTF-8') ?>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="monatsvorhersage-panel">
|
||||
<h3>Python-Snapshot</h3>
|
||||
<?php if ($pythonError !== null): ?>
|
||||
<div class="monatsvorhersage-alert"><?= htmlspecialchars($pythonError, ENT_QUOTES, 'UTF-8') ?></div>
|
||||
<?php else: ?>
|
||||
<div class="monatsvorhersage-meta">
|
||||
<div class="monatsvorhersage-stat">
|
||||
<strong>Lokalzeit</strong>
|
||||
<?= htmlspecialchars((string) ($pythonPayload['selected']['local_label'] ?? ''), ENT_QUOTES, 'UTF-8') ?>
|
||||
</div>
|
||||
<div class="monatsvorhersage-stat">
|
||||
<strong>UTC</strong>
|
||||
<?= htmlspecialchars((string) ($pythonPayload['selected']['utc_iso'] ?? ''), ENT_QUOTES, 'UTF-8') ?>
|
||||
</div>
|
||||
<div class="monatsvorhersage-stat">
|
||||
<strong>Lokale Sternzeit</strong>
|
||||
<?= $formatNumber($pythonPayload['sidereal']['local_hours'] ?? null, 2) ?> h
|
||||
</div>
|
||||
<div class="monatsvorhersage-stat">
|
||||
<strong>Mondphase</strong>
|
||||
<?= htmlspecialchars((string) ($pythonPayload['bodies']['moon']['phase_label'] ?? '–'), ENT_QUOTES, 'UTF-8') ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="monatsvorhersage-table-wrap">
|
||||
<table class="monatsvorhersage-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Objekt</th>
|
||||
<th>Höhe</th>
|
||||
<th>Azimut</th>
|
||||
<th>RA</th>
|
||||
<th>Dekl.</th>
|
||||
<th>Mag.</th>
|
||||
<th>Sichtbar</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($bodyRows as $row): ?>
|
||||
<tr>
|
||||
<td><?= htmlspecialchars((string) ($row['label'] ?? '–'), ENT_QUOTES, 'UTF-8') ?></td>
|
||||
<td><?= $formatNumber($row['altitude_deg'] ?? null, 1) ?>°</td>
|
||||
<td><?= $formatNumber($row['azimuth_deg'] ?? null, 1) ?>°</td>
|
||||
<td><?= $formatNumber($row['ra_hours'] ?? null, 2) ?> h</td>
|
||||
<td><?= $formatNumber($row['dec_deg'] ?? null, 1) ?>°</td>
|
||||
<td><?= $formatNumber($row['magnitude'] ?? null, 1) ?></td>
|
||||
<td>
|
||||
<span class="monatsvorhersage-pill <?= !empty($row['visible']) ? 'monatsvorhersage-pill--up' : 'monatsvorhersage-pill--down' ?>">
|
||||
<?= !empty($row['visible']) ? 'über Horizont' : 'unter Horizont' ?>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
|
||||
<section class="monatsvorhersage-panel">
|
||||
<h3>Nächste Schritte</h3>
|
||||
<ol class="monatsvorhersage-list">
|
||||
<li>Python ergänzt als Nächstes Sterne, Messier-Objekte und Sternbildlinien für genau diesen Monatszeitpunkt.</li>
|
||||
<li>Danach ziehen wir die Projektionen und das serverseitige Bildrendering nach, damit die Ausgabe cronjob-tauglich als PNG erzeugt werden kann.</li>
|
||||
<li>Die bisherige Browser-Vorschau bleibt vorerst als fachliche Referenz erhalten.</li>
|
||||
</ol>
|
||||
<div class="monatsvorhersage-actions">
|
||||
<a class="monatsvorhersage-link" href="monatsvorhersage_old.php?year=<?= (int) $selectedYear ?>&month=<?= (int) $selectedMonth ?>">Bisherige Vorschau öffnen</a>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<?php require __DIR__ . '/footer.php'; ?>
|
||||
File diff suppressed because it is too large
Load Diff
+135
-6
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
import base64
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
@@ -261,6 +262,128 @@ def action_planet_rise_set(args: list[str]) -> dict:
|
||||
}
|
||||
|
||||
|
||||
def serialize_body_position(
|
||||
key: str,
|
||||
label: str,
|
||||
body: astronomy.Body,
|
||||
time_value: astronomy.Time,
|
||||
observer: astronomy.Observer,
|
||||
) -> dict:
|
||||
eq = astronomy.Equator(body, time_value, observer, True, True)
|
||||
hor = astronomy.Horizon(time_value, observer, eq.ra, eq.dec, astronomy.Refraction.Normal)
|
||||
|
||||
payload = {
|
||||
"key": key,
|
||||
"label": label,
|
||||
"ra_hours": float(eq.ra),
|
||||
"dec_deg": float(eq.dec),
|
||||
"azimuth_deg": float(hor.azimuth),
|
||||
"altitude_deg": float(hor.altitude),
|
||||
"visible": float(hor.altitude) >= 0.0,
|
||||
}
|
||||
|
||||
try:
|
||||
illum = astronomy.Illumination(body, time_value)
|
||||
payload["distance_au"] = float(illum.geo_dist)
|
||||
payload["phase_fraction"] = float(illum.phase_fraction)
|
||||
payload["magnitude"] = float(illum.mag)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return payload
|
||||
|
||||
|
||||
def action_month_sky_context(args: list[str]) -> dict:
|
||||
if len(args) != 8:
|
||||
fail(
|
||||
"Aktion month_sky_context erwartet 8 Argumente: latitude longitude elevation year month hour minute timezone",
|
||||
extra={"argv": args},
|
||||
)
|
||||
|
||||
latitude = parse_float(args[0], "Latitude")
|
||||
longitude = parse_float(args[1], "Longitude")
|
||||
elevation = parse_float(args[2], "Elevation")
|
||||
|
||||
try:
|
||||
year = int(args[3])
|
||||
month = int(args[4])
|
||||
hour = int(args[5])
|
||||
minute = int(args[6])
|
||||
except ValueError as exc:
|
||||
fail("Jahr, Monat oder Uhrzeit sind ungueltig.", extra={"details": str(exc), "argv": args})
|
||||
|
||||
if month < 1 or month > 12:
|
||||
fail("Monat muss zwischen 1 und 12 liegen.", extra={"month": month})
|
||||
if hour < 0 or hour > 23:
|
||||
fail("Stunde muss zwischen 0 und 23 liegen.", extra={"hour": hour})
|
||||
if minute < 0 or minute > 59:
|
||||
fail("Minute muss zwischen 0 und 59 liegen.", extra={"minute": minute})
|
||||
|
||||
timezone_name = args[7]
|
||||
try:
|
||||
tz = ZoneInfo(timezone_name)
|
||||
except Exception as exc:
|
||||
fail("Zeitzone ist ungueltig oder auf dem Server nicht verfuegbar.", extra={"details": str(exc)})
|
||||
|
||||
local_dt = datetime(year, month, 15, hour, minute, 0, tzinfo=tz)
|
||||
utc_dt = local_dt.astimezone(timezone.utc)
|
||||
time_value = dt_to_time(utc_dt)
|
||||
observer = astronomy.Observer(latitude, longitude, elevation)
|
||||
|
||||
gst_hours = float(astronomy.SiderealTime(time_value))
|
||||
lst_hours = normalize_degrees(gst_hours * 15.0 + longitude) / 15.0
|
||||
|
||||
sun = serialize_body_position("sun", "Sonne", astronomy.Body.Sun, time_value, observer)
|
||||
moon = serialize_body_position("moon", "Mond", astronomy.Body.Moon, time_value, observer)
|
||||
|
||||
moon_phase_angle = normalize_degrees(astronomy.MoonPhase(time_value))
|
||||
moon_age_days = moon_phase_angle / 360.0 * SYNODIC_MONTH
|
||||
moon["phase_angle_deg"] = float(moon_phase_angle)
|
||||
moon["phase_label"] = get_phase_label(moon_age_days)
|
||||
moon["age_days"] = float(moon_age_days)
|
||||
moon["waxing"] = moon_phase_angle <= 180.0
|
||||
|
||||
planets = [
|
||||
serialize_body_position("mercury", "Merkur", astronomy.Body.Mercury, time_value, observer),
|
||||
serialize_body_position("venus", "Venus", astronomy.Body.Venus, time_value, observer),
|
||||
serialize_body_position("mars", "Mars", astronomy.Body.Mars, time_value, observer),
|
||||
serialize_body_position("jupiter", "Jupiter", astronomy.Body.Jupiter, time_value, observer),
|
||||
serialize_body_position("saturn", "Saturn", astronomy.Body.Saturn, time_value, observer),
|
||||
serialize_body_position("uranus", "Uranus", astronomy.Body.Uranus, time_value, observer),
|
||||
serialize_body_position("neptune", "Neptun", astronomy.Body.Neptune, time_value, observer),
|
||||
]
|
||||
|
||||
return {
|
||||
"ok": True,
|
||||
"action": "month_sky_context",
|
||||
"selected": {
|
||||
"year": year,
|
||||
"month": month,
|
||||
"day": 15,
|
||||
"hour": hour,
|
||||
"minute": minute,
|
||||
"timezone": timezone_name,
|
||||
"local_iso": local_dt.isoformat(),
|
||||
"utc_iso": utc_dt.isoformat().replace("+00:00", "Z"),
|
||||
"local_label": local_dt.strftime("%d.%m.%Y %H:%M") + f" {timezone_name}",
|
||||
},
|
||||
"observer": {
|
||||
"latitude": latitude,
|
||||
"longitude": longitude,
|
||||
"elevation": elevation,
|
||||
},
|
||||
"sidereal": {
|
||||
"greenwich_hours": gst_hours,
|
||||
"local_hours": lst_hours,
|
||||
},
|
||||
"bodies": {
|
||||
"sun": sun,
|
||||
"moon": moon,
|
||||
"planets": planets,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def classify_visibility(sun_alt_deg: float) -> dict:
|
||||
if sun_alt_deg > -5:
|
||||
return {"key": "day", "label": "Tag"}
|
||||
@@ -1032,7 +1155,7 @@ def _bisect_crossing(satrec, obs_ecef, lat_rad, lon_rad, t1: datetime, t2: datet
|
||||
def action_satellite_passes(args: list[str]) -> dict:
|
||||
if len(args) != 7:
|
||||
fail(
|
||||
"Aktion satellite_passes erwartet 7 Argumente: latitude longitude elevation timezone window_start_iso window_end_iso satellites_json",
|
||||
"Aktion satellite_passes erwartet 7 Argumente: latitude longitude elevation timezone window_start_iso window_end_iso satellites_payload",
|
||||
extra={"argv": args},
|
||||
)
|
||||
|
||||
@@ -1042,7 +1165,7 @@ def action_satellite_passes(args: list[str]) -> dict:
|
||||
timezone_name = args[3]
|
||||
window_start_iso = args[4]
|
||||
window_end_iso = args[5]
|
||||
satellites_json = args[6]
|
||||
satellites_payload = args[6]
|
||||
|
||||
try:
|
||||
tz = ZoneInfo(timezone_name)
|
||||
@@ -1056,9 +1179,10 @@ def action_satellite_passes(args: list[str]) -> dict:
|
||||
fail("Zeitfenster-ISO ungueltig.", extra={"details": str(exc)})
|
||||
|
||||
try:
|
||||
satellites_json = base64.b64decode(satellites_payload.encode("ascii")).decode("utf-8")
|
||||
satellites = json.loads(satellites_json)
|
||||
except json.JSONDecodeError as exc:
|
||||
fail("satellites_json ist kein gueltiges JSON.", extra={"details": str(exc)})
|
||||
except (ValueError, UnicodeDecodeError, json.JSONDecodeError) as exc:
|
||||
fail("satellites_payload ist ungueltig.", extra={"details": str(exc)})
|
||||
|
||||
try:
|
||||
import os as _os
|
||||
@@ -1261,7 +1385,7 @@ def action_satellite_passes(args: list[str]) -> dict:
|
||||
|
||||
def main() -> None:
|
||||
if len(sys.argv) < 2:
|
||||
fail("Es wurde keine Aktion uebergeben.", extra={"available_actions": ["sun_moon_rise_set", "moon_star_occultations", "moon_phase_details", "satellite_passes"]})
|
||||
fail("Es wurde keine Aktion uebergeben.", extra={"available_actions": ["sun_moon_rise_set", "moon_star_occultations", "moon_phase_details", "satellite_passes", "planet_rise_set", "month_sky_context"]})
|
||||
|
||||
action = sys.argv[1]
|
||||
args = sys.argv[2:]
|
||||
@@ -1291,7 +1415,12 @@ def main() -> None:
|
||||
print(json.dumps(result, ensure_ascii=False))
|
||||
return
|
||||
|
||||
fail("Unbekannte Aktion.", extra={"action": action, "available_actions": ["sun_moon_rise_set", "moon_star_occultations", "moon_phase_details", "satellite_passes", "planet_rise_set"]})
|
||||
if action == "month_sky_context":
|
||||
result = action_month_sky_context(args)
|
||||
print(json.dumps(result, ensure_ascii=False))
|
||||
return
|
||||
|
||||
fail("Unbekannte Aktion.", extra={"action": action, "available_actions": ["sun_moon_rise_set", "moon_star_occultations", "moon_phase_details", "satellite_passes", "planet_rise_set", "month_sky_context"]})
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user