Files
skyview.astronomiemuseum.de/public/beobachtungsvorschlaege.php
T
2026-09-15 15:58:51 +02:00

234 lines
18 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?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 &amp; 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'; ?>