451 lines
15 KiB
PHP
451 lines
15 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
ini_set('display_errors', '1');
|
|
error_reporting(E_ALL);
|
|
session_start();
|
|
|
|
require_once __DIR__ . '/auth_helpers.php';
|
|
applyLocalDevLoginBypass();
|
|
|
|
$pageTitle = 'Ephemeriden - AstroTools';
|
|
$headerIntroPre = 'Berechnungen';
|
|
$headerIntroTitle = 'Ephemeriden';
|
|
$headerIntroSub = '';
|
|
|
|
$loggedIn = isset($_SESSION['user_id']);
|
|
$defaultLocation = null;
|
|
|
|
if ($loggedIn && isset($_SESSION['user_id'])) {
|
|
try {
|
|
$cfg = require __DIR__ . '/../config/database.php';
|
|
$pdo = new PDO(
|
|
sprintf('mysql:host=%s;dbname=%s;charset=%s', $cfg['host'], $cfg['dbname'], $cfg['charset'] ?? 'utf8mb4'),
|
|
$cfg['user'],
|
|
$cfg['pass'],
|
|
[
|
|
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
|
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
|
]
|
|
);
|
|
|
|
$stmtLoc = $pdo->prepare(
|
|
'SELECT id, 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;
|
|
} catch (Throwable $e) {
|
|
$defaultLocation = null;
|
|
}
|
|
}
|
|
|
|
$planetOptions = [
|
|
'Sun' => 'Sonne',
|
|
'Moon' => 'Mond',
|
|
'Mercury' => 'Merkur',
|
|
'Venus' => 'Venus',
|
|
'Mars' => 'Mars',
|
|
'Jupiter' => 'Jupiter',
|
|
'Saturn' => 'Saturn',
|
|
'Uranus' => 'Uranus',
|
|
'Neptune' => 'Neptun',
|
|
];
|
|
|
|
$intervalOptions = [
|
|
'1' => '1 Minute',
|
|
'10' => '10 Minuten',
|
|
'30' => '30 Minuten',
|
|
'60' => '1 Stunde',
|
|
'360' => '6 Stunden',
|
|
'720' => '12 Stunden',
|
|
'1440' => '1 Tag',
|
|
'10080' => '7 Tage',
|
|
'20160' => '14 Tage',
|
|
'43200' => '30 Tage',
|
|
];
|
|
|
|
$rangeUnitOptions = [
|
|
'minutes' => 'Minuten',
|
|
'hours' => 'Stunden',
|
|
'days' => 'Tage',
|
|
'weeks' => 'Wochen',
|
|
'months' => 'Monate',
|
|
'years' => 'Jahre',
|
|
];
|
|
|
|
$selectedBody = (string) ($_POST['body'] ?? 'Mercury');
|
|
if (!isset($planetOptions[$selectedBody])) {
|
|
$selectedBody = 'Mercury';
|
|
}
|
|
|
|
$selectedInterval = (string) ($_POST['interval_minutes'] ?? '1440');
|
|
if (!isset($intervalOptions[$selectedInterval])) {
|
|
$selectedInterval = '1440';
|
|
}
|
|
|
|
$selectedRangeUnit = (string) ($_POST['range_unit'] ?? 'days');
|
|
if (!isset($rangeUnitOptions[$selectedRangeUnit])) {
|
|
$selectedRangeUnit = 'days';
|
|
}
|
|
|
|
$rangeValueInput = trim((string) ($_POST['range_value'] ?? '7'));
|
|
if ($rangeValueInput === '') {
|
|
$rangeValueInput = '7';
|
|
}
|
|
|
|
function ephemeriden_h(mixed $value): string
|
|
{
|
|
return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
|
|
}
|
|
|
|
function ephemeriden_slug(string $value): string
|
|
{
|
|
$value = strtolower(trim($value));
|
|
$value = str_replace(
|
|
['ä', 'ö', 'ü', 'ß', ' '],
|
|
['ae', 'oe', 'ue', 'ss', '-'],
|
|
$value
|
|
);
|
|
$value = preg_replace('/[^a-z0-9\-]+/', '-', $value) ?? $value;
|
|
$value = trim($value, '-');
|
|
return $value !== '' ? $value : 'ephemeriden';
|
|
}
|
|
|
|
function runEphemeridenPythonApi(string $action, array $args): array
|
|
{
|
|
$scriptPath = __DIR__ . '/py/ephemeriden_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,
|
|
];
|
|
}
|
|
|
|
$pageLocation = null;
|
|
if (is_array($defaultLocation) && $defaultLocation !== []) {
|
|
$pageLocation = [
|
|
'name' => trim((string) ($defaultLocation['name'] ?? '')),
|
|
'latitude' => isset($defaultLocation['latitude']) ? (float) $defaultLocation['latitude'] : null,
|
|
'longitude' => isset($defaultLocation['longitude']) ? (float) $defaultLocation['longitude'] : null,
|
|
'elevation' => isset($defaultLocation['elevation']) && $defaultLocation['elevation'] !== null ? (float) $defaultLocation['elevation'] : 0.0,
|
|
'timezone' => (string) ($defaultLocation['timezone'] ?? 'Europe/Berlin'),
|
|
];
|
|
}
|
|
|
|
$defaultLocationName = $pageLocation['name'] ?? '';
|
|
$errorMessage = '';
|
|
$ephemerisRows = [];
|
|
$exportFormat = (string) ($_POST['export_format'] ?? '');
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
if ($pageLocation === null || $pageLocation['latitude'] === null || $pageLocation['longitude'] === null) {
|
|
$errorMessage = 'Kein gültiger Standard-Standort verfügbar. Bitte zuerst in den Einstellungen einen Standard-Standort setzen.';
|
|
} elseif (!ctype_digit($rangeValueInput) || (int) $rangeValueInput < 1 || (int) $rangeValueInput > 31) {
|
|
$errorMessage = 'Der Zeitraum muss als ganze Zahl zwischen 1 und 31 angegeben werden.';
|
|
} else {
|
|
$pythonResult = runEphemeridenPythonApi('planet_ephemeris', [
|
|
(string) $pageLocation['latitude'],
|
|
(string) $pageLocation['longitude'],
|
|
(string) $pageLocation['elevation'],
|
|
(string) $pageLocation['timezone'],
|
|
$selectedBody,
|
|
$selectedInterval,
|
|
$rangeValueInput,
|
|
$selectedRangeUnit,
|
|
]);
|
|
|
|
if (!$pythonResult['ok']) {
|
|
$errorMessage = (string) (
|
|
$pythonResult['data']['error']
|
|
?? $pythonResult['error']
|
|
?? 'Die Ephemeriden konnten nicht berechnet werden.'
|
|
);
|
|
} else {
|
|
$ephemerisRows = $pythonResult['data']['rows'] ?? [];
|
|
if (!is_array($ephemerisRows)) {
|
|
$ephemerisRows = [];
|
|
}
|
|
|
|
if ($exportFormat === 'csv' && $ephemerisRows !== []) {
|
|
$filename = 'ephemeriden-' . ephemeriden_slug((string) ($planetOptions[$selectedBody] ?? 'objekt')) . '.csv';
|
|
|
|
header('Content-Type: text/csv; charset=utf-8');
|
|
header('Content-Disposition: attachment; filename="' . $filename . '"');
|
|
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
|
|
|
|
echo "\xEF\xBB\xBF";
|
|
$handle = fopen('php://output', 'wb');
|
|
if ($handle === false) {
|
|
exit;
|
|
}
|
|
|
|
fputcsv($handle, ['Name des Objekts', 'Datum', 'Uhrzeit', 'RA (J2000)', 'RA (J2000, dezimal h)', 'Dek (J2000)', 'Dek (J2000, dezimal °)', 'Aufgang', 'Untergang'], ';', '"', '\\');
|
|
foreach ($ephemerisRows as $row) {
|
|
fputcsv($handle, [
|
|
(string) ($row['object_name'] ?? ''),
|
|
(string) ($row['date_local'] ?? ''),
|
|
(string) ($row['time_local'] ?? ''),
|
|
(string) ($row['ra'] ?? ''),
|
|
(string) ($row['ra_decimal_hours'] ?? ''),
|
|
(string) ($row['dec'] ?? ''),
|
|
(string) ($row['dec_decimal_deg'] ?? ''),
|
|
(string) (($row['rise'] ?? null) ?? '—'),
|
|
(string) (($row['set'] ?? null) ?? '—'),
|
|
], ';', '"', '\\');
|
|
}
|
|
|
|
fclose($handle);
|
|
exit;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
require __DIR__ . '/header.php';
|
|
|
|
?>
|
|
|
|
<style>
|
|
.ephemeriden-form {
|
|
display: grid;
|
|
gap: 0.8rem;
|
|
}
|
|
|
|
.ephemeriden-fields-row {
|
|
display: grid;
|
|
grid-template-columns: repeat(4, minmax(160px, 1fr)) auto;
|
|
gap: 0.8rem;
|
|
align-items: end;
|
|
}
|
|
|
|
.ephemeriden-submit-group {
|
|
display: flex;
|
|
align-items: flex-end;
|
|
}
|
|
|
|
.ephemeriden-results-head {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
justify-content: space-between;
|
|
gap: 1rem;
|
|
flex-wrap: wrap;
|
|
margin-bottom: 0.9rem;
|
|
}
|
|
|
|
.ephemeriden-results-note {
|
|
color: var(--text-dim);
|
|
font-size: 0.92rem;
|
|
}
|
|
|
|
.ephemeriden-export-form {
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
|
|
.ephemeriden-message {
|
|
margin-bottom: 0.9rem;
|
|
padding: 0.85rem 1rem;
|
|
border-radius: var(--radius);
|
|
border: 1px solid var(--border);
|
|
background: var(--bg-card2);
|
|
}
|
|
|
|
.ephemeriden-message.is-error {
|
|
border-color: rgba(190, 92, 92, 0.45);
|
|
color: #ffd7d7;
|
|
}
|
|
|
|
.ephemeriden-table-wrap {
|
|
overflow-x: auto;
|
|
}
|
|
|
|
.ephemeriden-table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
}
|
|
|
|
.ephemeriden-table th,
|
|
.ephemeriden-table td {
|
|
padding: 0.75rem 0.85rem;
|
|
text-align: left;
|
|
border-bottom: 1px solid var(--border);
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.ephemeriden-table th {
|
|
color: var(--gold-pale);
|
|
font-size: 0.82rem;
|
|
letter-spacing: 0.04em;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
.ephemeriden-table-empty td {
|
|
color: var(--text-dim);
|
|
white-space: normal;
|
|
}
|
|
|
|
@media (max-width: 900px) {
|
|
.ephemeriden-fields-row {
|
|
grid-template-columns: repeat(2, minmax(180px, 1fr));
|
|
}
|
|
}
|
|
|
|
@media (max-width: 640px) {
|
|
.ephemeriden-fields-row {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
</style>
|
|
|
|
<section class="card">
|
|
<form method="post" class="ephemeriden-form">
|
|
<div class="ephemeriden-fields-row">
|
|
<div class="form-group">
|
|
<label for="ephemeridenBody">Planet</label>
|
|
<select id="ephemeridenBody" name="body">
|
|
<?php foreach ($planetOptions as $bodyValue => $bodyLabel): ?>
|
|
<option value="<?= ephemeriden_h($bodyValue) ?>"<?= $bodyValue === $selectedBody ? ' selected' : '' ?>><?= ephemeriden_h($bodyLabel) ?></option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="ephemeridenInterval">Intervall</label>
|
|
<select id="ephemeridenInterval" name="interval_minutes">
|
|
<?php foreach ($intervalOptions as $intervalValue => $intervalLabel): ?>
|
|
<option value="<?= ephemeriden_h($intervalValue) ?>"<?= (string) $intervalValue === $selectedInterval ? ' selected' : '' ?>><?= ephemeriden_h($intervalLabel) ?></option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="ephemeridenRangeValue">Zeitraum ab heute</label>
|
|
<input
|
|
type="number"
|
|
id="ephemeridenRangeValue"
|
|
name="range_value"
|
|
min="1"
|
|
max="31"
|
|
step="1"
|
|
value="<?= ephemeriden_h($rangeValueInput) ?>"
|
|
inputmode="numeric"
|
|
>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="ephemeridenRangeUnit">Einheit</label>
|
|
<select id="ephemeridenRangeUnit" name="range_unit">
|
|
<?php foreach ($rangeUnitOptions as $rangeUnitValue => $rangeUnitLabel): ?>
|
|
<option value="<?= ephemeriden_h($rangeUnitValue) ?>"<?= $rangeUnitValue === $selectedRangeUnit ? ' selected' : '' ?>><?= ephemeriden_h($rangeUnitLabel) ?></option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="ephemeriden-submit-group">
|
|
<button type="submit" class="btn btn-primary">Berechnen</button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</section>
|
|
|
|
<section class="card mt-md">
|
|
<div class="ephemeriden-results-head">
|
|
<div>
|
|
<h2>Ephemeriden</h2>
|
|
<div class="ephemeriden-results-note">
|
|
Standard-Standort:
|
|
<strong><?= ephemeriden_h($defaultLocationName !== '' ? $defaultLocationName : 'nicht verfügbar') ?></strong>
|
|
</div>
|
|
</div>
|
|
<?php if ($ephemerisRows !== []): ?>
|
|
<form method="post" class="ephemeriden-export-form">
|
|
<input type="hidden" name="body" value="<?= ephemeriden_h($selectedBody) ?>">
|
|
<input type="hidden" name="interval_minutes" value="<?= ephemeriden_h($selectedInterval) ?>">
|
|
<input type="hidden" name="range_value" value="<?= ephemeriden_h($rangeValueInput) ?>">
|
|
<input type="hidden" name="range_unit" value="<?= ephemeriden_h($selectedRangeUnit) ?>">
|
|
<input type="hidden" name="export_format" value="csv">
|
|
<button type="submit" class="btn btn-secondary">Export CSV</button>
|
|
</form>
|
|
<?php endif; ?>
|
|
</div>
|
|
|
|
<?php if ($errorMessage !== ''): ?>
|
|
<div class="ephemeriden-message is-error"><?= ephemeriden_h($errorMessage) ?></div>
|
|
<?php endif; ?>
|
|
|
|
<div class="ephemeriden-table-wrap">
|
|
<table class="ephemeriden-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Name des Objekts</th>
|
|
<th>Datum</th>
|
|
<th>Uhrzeit</th>
|
|
<th>RA (J2000)</th>
|
|
<th>Dek (J2000)</th>
|
|
<th>Aufgang</th>
|
|
<th>Untergang</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php if ($ephemerisRows !== []): ?>
|
|
<?php foreach ($ephemerisRows as $row): ?>
|
|
<tr>
|
|
<td><?= ephemeriden_h((string) ($row['object_name'] ?? '')) ?></td>
|
|
<td><?= ephemeriden_h((string) ($row['date_local'] ?? '')) ?></td>
|
|
<td><?= ephemeriden_h((string) ($row['time_local'] ?? '')) ?></td>
|
|
<td><?= ephemeriden_h((string) ($row['ra'] ?? '')) ?></td>
|
|
<td><?= ephemeriden_h((string) ($row['dec'] ?? '')) ?></td>
|
|
<td><?= ephemeriden_h((string) (($row['rise'] ?? null) ?? '—')) ?></td>
|
|
<td><?= ephemeriden_h((string) (($row['set'] ?? null) ?? '—')) ?></td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
<?php elseif ($_SERVER['REQUEST_METHOD'] === 'POST' && $errorMessage === ''): ?>
|
|
<tr class="ephemeriden-table-empty">
|
|
<td colspan="7">Für die gewählte Anfrage wurden keine Tabellenzeilen erzeugt.</td>
|
|
</tr>
|
|
<?php else: ?>
|
|
<tr class="ephemeriden-table-empty">
|
|
<td colspan="7">Noch keine Berechnung ausgeführt.</td>
|
|
</tr>
|
|
<?php endif; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</section>
|
|
|
|
<?php require __DIR__ . '/footer.php'; ?>
|