393 lines
12 KiB
PHP
393 lines
12 KiB
PHP
<?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'; ?>
|