234 lines
18 KiB
PHP
234 lines
18 KiB
PHP
<?php
|
||
declare(strict_types=1);
|
||
|
||
session_start();
|
||
|
||
$pageTitle = 'Beobachtungsvorschläge';
|
||
$headerIntroPre = 'Beobachtung planen';
|
||
$headerIntroTitle = 'Beobachtungsvorschläge';
|
||
$headerIntroSub = 'Beobachtungsziele für eine Nacht am eigenen Teleskop eingrenzen.';
|
||
|
||
require __DIR__ . '/header.php';
|
||
|
||
try {
|
||
$observationTimezone = new DateTimeZone((string) ($currentLocation['timezone'] ?? 'Europe/Berlin'));
|
||
} catch (Exception $e) {
|
||
$observationTimezone = new DateTimeZone('Europe/Berlin');
|
||
}
|
||
$observationToday = (new DateTimeImmutable('now', $observationTimezone))->format('Y-m-d');
|
||
$_SESSION['observation_csrf'] ??= bin2hex(random_bytes(32));
|
||
?>
|
||
|
||
<style>
|
||
.observation-gui { display: grid; gap: 1.5rem; }
|
||
.observation-gui .card { margin-bottom: 0; min-width: 0; }
|
||
.observation-notice { padding: 0.85rem 1rem; border-left: 3px solid var(--gold); background: var(--bg-card2); border-radius: var(--radius); }
|
||
.observation-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1.5rem; }
|
||
.observation-fields { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1rem; }
|
||
.observation-gui .form-group { min-width: 0; margin-bottom: 0; }
|
||
.observation-gui .form-group input,
|
||
.observation-gui .form-group select { width: 100%; min-width: 0; box-sizing: border-box; color-scheme: dark; }
|
||
.observation-gui fieldset { min-width: 0; border: 0; padding: 0; margin: 0 0 1.5rem; }
|
||
.observation-gui fieldset:last-child { margin-bottom: 0; }
|
||
.observation-gui legend { font-weight: 600; margin-bottom: 0.65rem; }
|
||
.observation-help { color: var(--text-dim); font-size: 0.85rem; line-height: 1.6; margin: 0.6rem 0 0; }
|
||
.observation-wide { grid-column: 1 / -1; }
|
||
.observation-types { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0.75rem; }
|
||
.observation-choice { display: flex; align-items: center; gap: 0.65rem; cursor: pointer; }
|
||
.observation-choice input { accent-color: var(--gold); width: 1.1rem; height: 1.1rem; flex-shrink: 0; }
|
||
.observation-gui input:focus-visible,
|
||
.observation-gui select:focus-visible { outline: 2px solid var(--gold); outline-offset: 3px; }
|
||
.observation-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 1rem; margin-top: 1.5rem; }
|
||
.observation-actions button:disabled { opacity: 0.5; cursor: not-allowed; }
|
||
.observation-empty { padding: 1.5rem; text-align: center; border: 1px dashed var(--border); border-radius: var(--radius); }
|
||
.observation-table-wrap { overflow-x: auto; margin-top: 1rem; }
|
||
.observation-results-table { width: 100%; }
|
||
.observation-results-table td { vertical-align: top; }
|
||
.observation-results-table small { display: block; color: var(--text-dim); max-width: 22rem; }
|
||
.observation-results-table td:nth-child(n+3) { white-space: nowrap; }
|
||
.observation-sort { border: 0; background: transparent; color: inherit; font: inherit; text-transform: inherit; letter-spacing: inherit; text-align: left; cursor: pointer; padding: 0.35rem 0; }
|
||
.observation-sort:hover { color: var(--gold); }
|
||
.observation-sort:focus-visible { outline: 2px solid var(--gold); outline-offset: 3px; }
|
||
.observation-sort-indicator { margin-left: 0.35rem; }
|
||
.observation-gui [hidden] { display: none; }
|
||
@media (max-width: 760px) {
|
||
.observation-grid { grid-template-columns: 1fr; }
|
||
}
|
||
@media (max-width: 440px) {
|
||
.observation-fields, .observation-types { grid-template-columns: 1fr; }
|
||
.observation-gui .card { padding: 1.1rem; }
|
||
}
|
||
</style>
|
||
|
||
<form class="observation-gui" id="observation-form" action="ajax/observation_suggestions.php" method="post">
|
||
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['observation_csrf'], ENT_QUOTES, 'UTF-8') ?>">
|
||
<p class="observation-notice">Finde Ziele für deine Beobachtungsnacht. Die Sichtbarkeit wird in Schritten von fünf Minuten geprüft; Zeitfenster und beste Zeiten sind Näherungen.</p>
|
||
|
||
<section class="card" aria-labelledby="observation-selection-title">
|
||
<h2 id="observation-selection-title">Beobachtungsnacht und Standort</h2>
|
||
<div class="observation-fields">
|
||
<div class="form-group">
|
||
<label for="observation-date">Datum der Beobachtungsnacht</label>
|
||
<input type="date" id="observation-date" name="observation_date" value="<?= $observationToday ?>" min="1900-01-01" max="2100-12-31" required aria-describedby="observation-time-help">
|
||
</div>
|
||
<fieldset aria-describedby="observation-time-help">
|
||
<legend>Nachthälften</legend>
|
||
<div class="observation-types">
|
||
<label class="observation-choice"><input type="checkbox" name="night_halves[]" value="first" checked> Erste Nachthälfte</label>
|
||
<label class="observation-choice"><input type="checkbox" name="night_halves[]" value="second" checked> Zweite Nachthälfte</label>
|
||
</div>
|
||
</fieldset>
|
||
<div class="form-group">
|
||
<label for="observation-location">Beobachtungsstandort</label>
|
||
<input type="text" id="observation-location" value="<?= htmlspecialchars((string) ($currentLocation['name'] ?? 'Kein Standardstandort hinterlegt'), ENT_QUOTES, 'UTF-8') ?>" readonly aria-describedby="observation-location-help">
|
||
<p class="observation-help" id="observation-location-help">Es gilt immer dein Standardstandort aus dem Header.</p>
|
||
</div>
|
||
</div>
|
||
<p class="observation-help" id="observation-time-help">Das Datum bezeichnet die Nacht vom gewählten Abend bis zum folgenden Morgen. Die eingestellte Sonnenhöhe begrenzt den Beobachtungszeitraum; die Nachthälften teilen diesen in zwei gleich lange Abschnitte. Beide auswählen für die ganze Nacht.</p>
|
||
</section>
|
||
|
||
<div class="observation-grid">
|
||
<section class="card" aria-labelledby="observation-object-title">
|
||
<h2 id="observation-object-title">Eigenschaften der Objekte</h2>
|
||
<fieldset>
|
||
<legend>Scheinbare Größe – nur Deep Sky (Bogensekunden)</legend>
|
||
<div class="observation-fields">
|
||
<div class="form-group">
|
||
<label for="object-size-min">Minimum (″)</label>
|
||
<input type="number" id="object-size-min" name="object_size_min" min="0" step="any" placeholder="Keine Untergrenze" aria-describedby="object-size-help">
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="object-size-max">Maximum (″)</label>
|
||
<input type="number" id="object-size-max" name="object_size_max" min="0" step="any" value="500" placeholder="Keine Obergrenze" aria-describedby="object-size-help">
|
||
</div>
|
||
</div>
|
||
<p class="observation-help" id="object-size-help">Dieser Filter gilt nur für Galaxien, Sternhaufen und Nebel einschließlich planetarischer Nebel. Verglichen wird die größte Katalogausdehnung. Bei gesetzter Größengrenze werden Deep-Sky-Objekte ohne Größenangabe ausgelassen. 60″ = 1′.</p>
|
||
</fieldset>
|
||
<fieldset>
|
||
<legend>Helligkeit</legend>
|
||
<div class="form-group">
|
||
<label for="object-magnitude">Grenzgröße / schwächste Helligkeit (mag)</label>
|
||
<input type="number" id="object-magnitude" name="object_magnitude" step="0.1" value="12" placeholder="z. B. 12,0" aria-describedby="object-magnitude-help">
|
||
</div>
|
||
<p class="observation-help" id="object-magnitude-help">12 mag bedeutet: 12 mag oder heller. Kleinere und negative mag-Werte sind heller. Leer lassen für keine Grenze. Bei Galaxien und Nebeln sagt die Gesamthelligkeit allein wenig über die Erkennbarkeit aus.</p>
|
||
<label class="observation-choice" style="margin-top: 0.85rem;"><input type="checkbox" name="include_unknown_magnitude" value="1" aria-describedby="unknown-magnitude-help"> Auch Objekte ohne Helligkeitsangabe</label>
|
||
<p class="observation-help" id="unknown-magnitude-help">Bei gesetzter Grenzgröße werden diese sonst ausgelassen. Wenn ausgewählt, erscheinen sie am Ende als „Helligkeit unbekannt“; die Grenzgröße lässt sich für sie nicht prüfen.</p>
|
||
</fieldset>
|
||
<fieldset>
|
||
<legend>Objekttypen</legend>
|
||
<div class="observation-types">
|
||
<label class="observation-choice"><input type="checkbox" name="object_types[]" value="moon" checked> Mond</label>
|
||
<label class="observation-choice"><input type="checkbox" name="object_types[]" value="planets" checked> Planeten</label>
|
||
<label class="observation-choice"><input type="checkbox" name="object_types[]" value="open_clusters" checked> Offene Sternhaufen</label>
|
||
<label class="observation-choice"><input type="checkbox" name="object_types[]" value="globular_clusters" checked> Kugelsternhaufen</label>
|
||
<label class="observation-choice"><input type="checkbox" name="object_types[]" value="galaxies" checked> Galaxien & Galaxiengruppen</label>
|
||
<label class="observation-choice"><input type="checkbox" name="object_types[]" value="nebulae" checked aria-describedby="nebulae-help"> Nebel</label>
|
||
<label class="observation-choice"><input type="checkbox" name="object_types[]" value="planetary_nebulae" checked> Planetarische Nebel</label>
|
||
<label class="observation-choice"><input type="checkbox" name="object_types[]" value="double_stars" checked> Doppelsterne</label>
|
||
<label class="observation-choice"><input type="checkbox" name="object_types[]" value="variable_stars" checked> Veränderliche Sterne</label>
|
||
<label class="observation-choice"><input type="checkbox" name="object_types[]" value="comets" checked> Kometen</label>
|
||
</div>
|
||
<p class="observation-help" id="nebulae-help">„Nebel“ umfasst Emissions- und Reflexionsnebel, H-II-Regionen, Supernovaüberreste und Sternhaufen mit Nebel. Planetarische Nebel sind separat auswählbar.</p>
|
||
</fieldset>
|
||
</section>
|
||
|
||
<section class="card" aria-labelledby="observation-sky-title">
|
||
<h2 id="observation-sky-title">Himmelsbereich und Dunkelheit</h2>
|
||
<fieldset>
|
||
<legend>Höhe über dem Horizont</legend>
|
||
<div class="observation-fields">
|
||
<div class="form-group">
|
||
<label for="object-altitude-min">Minimum (°)</label>
|
||
<input type="number" id="object-altitude-min" name="object_altitude_min" min="0" max="90" step="1" value="20" aria-describedby="object-altitude-help">
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="object-altitude-max">Maximum (°)</label>
|
||
<input type="number" id="object-altitude-max" name="object_altitude_max" min="0" max="90" step="1" value="90" aria-describedby="object-altitude-help">
|
||
</div>
|
||
</div>
|
||
<p class="observation-help" id="object-altitude-help">0° = Horizont · 90° = Zenit. Eine Obergrenze kann bei eingeschränktem Einblick oder einer azimutalen Montierung hilfreich sein.</p>
|
||
</fieldset>
|
||
<fieldset>
|
||
<legend>Azimut / Himmelsrichtung</legend>
|
||
<div class="observation-fields">
|
||
<div class="form-group">
|
||
<label for="object-azimuth-start">Von (°)</label>
|
||
<input type="number" id="object-azimuth-start" name="object_azimuth_start" min="0" max="360" step="1" value="0" aria-describedby="object-azimuth-help">
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="object-azimuth-end">Bis (°)</label>
|
||
<input type="number" id="object-azimuth-end" name="object_azimuth_end" min="0" max="360" step="1" value="360" aria-describedby="object-azimuth-help">
|
||
</div>
|
||
</div>
|
||
<p class="observation-help" id="object-azimuth-help">Nord 0°/360° · Ost 90° · Süd 180° · West 270°.<br>Der Bereich verläuft über Ost, Süd und West. 0° bis 360° umfasst alle Richtungen; 300° bis 60° führt über Norden.</p>
|
||
</fieldset>
|
||
<fieldset>
|
||
<legend>Dunkelheit</legend>
|
||
<div class="form-group">
|
||
<label for="sun-altitude-max">Sonne höchstens auf Höhe (°)</label>
|
||
<input type="number" id="sun-altitude-max" name="sun_altitude_max" min="-90" max="0" step="1" value="-12" aria-describedby="sun-altitude-help">
|
||
</div>
|
||
<p class="observation-help" id="sun-altitude-help">Negative Werte liegen unter dem Horizont. −6°: Ende der bürgerlichen, −12°: Ende der nautischen, −18°: Ende der astronomischen Abenddämmerung.</p>
|
||
</fieldset>
|
||
<fieldset>
|
||
<legend>Mondlicht berücksichtigen (optional)</legend>
|
||
<div class="form-group">
|
||
<label for="moon-separation-min">Mindestabstand zum Mond (°)</label>
|
||
<input type="number" id="moon-separation-min" name="moon_separation_min" min="0" max="180" step="1" placeholder="Keine Einschränkung" aria-describedby="moon-separation-help">
|
||
</div>
|
||
<p class="observation-help" id="moon-separation-help">Für andere Ziele bei Mond über dem Horizont gedacht; gilt nicht für den Mond als Beobachtungsziel.</p>
|
||
</fieldset>
|
||
</section>
|
||
</div>
|
||
|
||
<section class="card" aria-labelledby="observation-suggestions-title">
|
||
<h2 id="observation-suggestions-title">Beobachtungsvorschläge</h2>
|
||
<div class="observation-actions">
|
||
<button type="submit" class="btn btn-primary" id="observation-submit">Vorschläge berechnen</button>
|
||
<span class="observation-help" id="observation-status" role="status" aria-live="polite">Noch keine Vorschläge berechnet.</span>
|
||
</div>
|
||
<p id="observation-error" role="alert" hidden></p>
|
||
<div id="observation-results" hidden>
|
||
<p class="observation-help" id="observation-summary"></p>
|
||
<ul class="observation-help" id="observation-warnings"></ul>
|
||
<div class="observation-actions">
|
||
<label class="observation-choice"><input type="checkbox" id="observation-limit-enabled" checked aria-describedby="observation-group-limit-help"> Auf die hellsten je Gruppe begrenzen</label>
|
||
<div class="form-group">
|
||
<label for="observation-group-limit">Maximal pro Objektgruppe</label>
|
||
<input type="number" id="observation-group-limit" form="observation-display-options" min="1" step="1" value="30" required aria-describedby="observation-group-limit-help">
|
||
</div>
|
||
</div>
|
||
<p class="observation-help" id="observation-group-limit-help">Je Objekttyp aus der Auswahl bleiben die X hellsten Treffer nach der angezeigten mag-Helligkeit. Bei gleicher Helligkeit zählt die größere Höhe. Objekte ohne Helligkeitsangabe werden bei dieser Begrenzung ausgelassen.</p>
|
||
<p class="observation-help" id="observation-limit-status" role="status" aria-live="polite"></p>
|
||
<label class="observation-choice"><input type="checkbox" id="observation-multi-sort"> Weitere Sortierkriterien hinzufügen (auch ohne Umschalttaste)</label>
|
||
<p class="observation-help" id="observation-sort-summary" role="status" aria-live="polite"></p>
|
||
<div class="observation-table-wrap">
|
||
<table class="data-table observation-results-table">
|
||
<caption>Klick auf eine Überschrift setzt das Hauptkriterium. Umschalt + Klick ergänzt weitere Kriterien; erneuter Klick kehrt die Richtung um. Das nächste Kriterium entscheidet bei gleichen Werten. Fehlende Werte folgen innerhalb der jeweiligen Sortiergruppe am Ende.</caption>
|
||
<thead><tr>
|
||
<th scope="col"><button type="button" class="observation-sort" data-sort="name">Objekt<span class="observation-sort-indicator" aria-hidden="true">↕</span></button></th>
|
||
<th scope="col"><button type="button" class="observation-sort" data-sort="type">Typ<span class="observation-sort-indicator" aria-hidden="true">↕</span></button></th>
|
||
<th scope="col"><button type="button" class="observation-sort" data-sort="magnitude">Helligkeit<span class="observation-sort-indicator" aria-hidden="true">↕</span></button></th>
|
||
<th scope="col"><button type="button" class="observation-sort" data-sort="size">Größe<span class="observation-sort-indicator" aria-hidden="true">↕</span></button></th>
|
||
<th scope="col"><button type="button" class="observation-sort" data-sort="window" title="Nach Beginn des ersten passenden Zeitfensters sortieren">Passendes Zeitfenster (ca.)<span class="observation-sort-indicator" aria-hidden="true">↕</span></button></th>
|
||
<th scope="col"><button type="button" class="observation-sort" data-sort="time" title="Zeitpunkt der größten geprüften Höhe innerhalb deiner Filter">Zeit der größten Höhe (ca.)<span class="observation-sort-indicator" aria-hidden="true">↕</span></button></th>
|
||
<th scope="col" aria-sort="descending"><button type="button" class="observation-sort" data-sort="altitude">Höhe<span class="observation-sort-indicator" aria-hidden="true">↓</span></button></th>
|
||
<th scope="col"><button type="button" class="observation-sort" data-sort="azimuth">Azimut<span class="observation-sort-indicator" aria-hidden="true">↕</span></button></th>
|
||
</tr></thead>
|
||
<tbody id="observation-rows"></tbody>
|
||
</table>
|
||
</div>
|
||
<div class="observation-actions">
|
||
<button type="button" class="btn btn-secondary" id="observation-previous">Zurück</button>
|
||
<span id="observation-pagination"></span>
|
||
<button type="button" class="btn btn-secondary" id="observation-next">Weiter</button>
|
||
</div>
|
||
</div>
|
||
<noscript><p>Bitte JavaScript aktivieren, um die Vorschläge auf dieser Seite anzuzeigen.</p></noscript>
|
||
</section>
|
||
</form>
|
||
<form id="observation-display-options"></form>
|
||
<script src="js/observation_suggestions.js" defer></script>
|
||
|
||
<?php require __DIR__ . '/footer.php'; ?>
|