diff --git a/public/ephemeriden.php b/public/ephemeriden.php index 9f59f92..f001f42 100644 --- a/public/ephemeriden.php +++ b/public/ephemeriden.php @@ -19,6 +19,13 @@ $favoriteMinorplanetOptions = []; $favoriteCometOptions = []; $minorplanetPayloads = []; $cometPayloads = []; +$chartStarData = []; +$chartLineStarData = []; +$chartConstellationLines = []; +$ephemerisChartConfigJson = 'null'; +$ephemerisStarDataJson = '[]'; +$ephemerisLineStarDataJson = '[]'; +$ephemerisConstellationLinesJson = '[]'; function decodeMpcPackedDateChar(string $value): ?int { @@ -205,12 +212,96 @@ if ($loggedIn && isset($_SESSION['user_id'])) { 'inclinationDeg' => isset($comet['inclination_deg']) ? (float) $comet['inclination_deg'] : null, ]; } + + $stmtStars = $pdo->query( + "SELECT hip, proper, bayer, con, spect, mag, ci, rarad, decrad + FROM star_hipparcos + WHERE mag IS NOT NULL + AND rarad IS NOT NULL + AND decrad IS NOT NULL + AND mag <= 6.0 + ORDER BY mag ASC" + ); + + while ($row = $stmtStars->fetch()) { + $chartStarData[] = [ + 'hip' => $row['hip'] !== null ? (int) $row['hip'] : null, + 'proper' => trim((string) ($row['proper'] ?? '')), + 'bayer' => trim((string) ($row['bayer'] ?? '')), + 'con' => trim((string) ($row['con'] ?? '')), + 'spect' => trim((string) ($row['spect'] ?? '')), + 'ra' => (float) $row['rarad'], + 'dec' => (float) $row['decrad'], + 'mag' => (float) $row['mag'], + 'ci' => $row['ci'] !== null ? (float) $row['ci'] : null, + ]; + } + + $constellationIndex = []; + $ensureConstellation = static function (string $id) use (&$constellationIndex): void { + if (!isset($constellationIndex[$id])) { + $constellationIndex[$id] = [ + 'id' => $id, + 'lines' => [], + ]; + } + }; + + $lineStmt = $pdo->query( + "SELECT cl.constellation_id, cl.line_index, clp.point_index, clp.hip_number + FROM constellation_lines cl + INNER JOIN constellation_line_points clp ON clp.line_id = cl.id + ORDER BY cl.constellation_id, cl.line_index, clp.point_index" + ); + + foreach ($lineStmt as $row) { + $constellationId = (string) $row['constellation_id']; + $lineIndex = (int) $row['line_index']; + $ensureConstellation($constellationId); + $constellationIndex[$constellationId]['lines'][$lineIndex] ??= []; + $constellationIndex[$constellationId]['lines'][$lineIndex][] = (int) $row['hip_number']; + } + + foreach ($constellationIndex as &$constellation) { + if ($constellation['lines']) { + ksort($constellation['lines']); + $constellation['lines'] = array_values($constellation['lines']); + } + } + unset($constellation); + + $chartConstellationLines = array_values($constellationIndex); + + $lineStarStmt = $pdo->query( + "SELECT DISTINCT sh.hip, sh.proper, sh.bayer, sh.con, sh.spect, sh.mag, sh.ci, sh.rarad, sh.decrad + FROM constellation_line_points clp + INNER JOIN star_hipparcos sh ON sh.hip = clp.hip_number + WHERE sh.rarad IS NOT NULL + AND sh.decrad IS NOT NULL" + ); + + while ($row = $lineStarStmt->fetch()) { + $chartLineStarData[] = [ + 'hip' => $row['hip'] !== null ? (int) $row['hip'] : null, + 'proper' => trim((string) ($row['proper'] ?? '')), + 'bayer' => trim((string) ($row['bayer'] ?? '')), + 'con' => trim((string) ($row['con'] ?? '')), + 'spect' => trim((string) ($row['spect'] ?? '')), + 'ra' => (float) $row['rarad'], + 'dec' => (float) $row['decrad'], + 'mag' => $row['mag'] !== null ? (float) $row['mag'] : 99.0, + 'ci' => $row['ci'] !== null ? (float) $row['ci'] : null, + ]; + } } catch (Throwable $e) { $defaultLocation = null; $favoriteMinorplanetOptions = []; $favoriteCometOptions = []; $minorplanetPayloads = []; $cometPayloads = []; + $chartStarData = []; + $chartLineStarData = []; + $chartConstellationLines = []; } } @@ -343,6 +434,8 @@ if (is_array($defaultLocation) && $defaultLocation !== []) { $defaultLocationName = $pageLocation['name'] ?? ''; $errorMessage = ''; $ephemerisRows = []; +$ephemerisChartRowsJson = '[]'; +$ephemerisWindowStartIso = null; $exportFormat = (string) ($_POST['export_format'] ?? ''); if ($_SERVER['REQUEST_METHOD'] === 'POST') { @@ -350,8 +443,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $errorMessage = 'Kein gültiger Standard-Standort verfügbar. Bitte zuerst in den Einstellungen einen Standard-Standort setzen.'; } elseif (false && (str_starts_with($selectedBody, 'comet:') || str_starts_with($selectedBody, 'minorplanet:'))) { $errorMessage = 'Favoriten-Kometen und Favoriten-Kleinplaneten sind jetzt in der Auswahl sichtbar. Die eigentliche Ephemeriden-Berechnung dafür binden wir als Nächstes an.'; - } elseif (!ctype_digit($rangeValueInput) || (int) $rangeValueInput < 1 || (int) $rangeValueInput > 31) { - $errorMessage = 'Der Zeitraum muss als ganze Zahl zwischen 1 und 31 angegeben werden.'; + } elseif (!ctype_digit($rangeValueInput) || (int) $rangeValueInput < 1 || (int) $rangeValueInput > 100) { + $errorMessage = 'Der Zeitraum muss als ganze Zahl zwischen 1 und 100 angegeben werden.'; } else { $pythonAction = 'planet_ephemeris'; $pythonArgs = [ @@ -403,6 +496,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { ); } else { $ephemerisRows = $pythonResult['data']['rows'] ?? []; + $ephemerisWindowStartIso = isset($pythonResult['data']['window']['local_start']) + ? (string) $pythonResult['data']['window']['local_start'] + : null; if (!is_array($ephemerisRows)) { $ephemerisRows = []; } @@ -442,6 +538,45 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { } } +if ($ephemerisRows !== []) { + $chartRows = []; + foreach ($ephemerisRows as $row) { + $azimuthDeg = isset($row['azimuth_deg']) ? (float) $row['azimuth_deg'] : null; + $altitudeDeg = isset($row['altitude_deg']) ? (float) $row['altitude_deg'] : null; + if ($azimuthDeg === null || $altitudeDeg === null) { + continue; + } + + $chartRows[] = [ + 'object_name' => (string) ($row['object_name'] ?? ''), + 'date_local' => (string) ($row['date_local'] ?? ''), + 'time_local' => (string) ($row['time_local'] ?? ''), + 'local_iso' => (string) ($row['local_iso'] ?? ''), + 'ra_decimal_hours' => isset($row['ra_decimal_hours']) ? (float) $row['ra_decimal_hours'] : null, + 'dec_decimal_deg' => isset($row['dec_decimal_deg']) ? (float) $row['dec_decimal_deg'] : null, + 'azimuth_deg' => $azimuthDeg, + 'altitude_deg' => $altitudeDeg, + ]; + } + + $ephemerisChartRowsJson = json_encode($chartRows, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?: '[]'; + $ephemerisChartConfigJson = json_encode([ + 'location' => [ + 'name' => $defaultLocationName, + 'latitude' => $pageLocation['latitude'] ?? null, + 'longitude' => $pageLocation['longitude'] ?? null, + 'elevation' => $pageLocation['elevation'] ?? 0.0, + 'timezone' => $pageLocation['timezone'] ?? 'Europe/Berlin', + ], + 'utcTimeIso' => $ephemerisWindowStartIso, + 'starLimit' => 4.4, + 'starLabelLimit' => 2.0, + ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?: 'null'; + $ephemerisStarDataJson = json_encode($chartStarData, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?: '[]'; + $ephemerisLineStarDataJson = json_encode($chartLineStarData, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?: '[]'; + $ephemerisConstellationLinesJson = json_encode($chartConstellationLines, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?: '[]'; +} + require __DIR__ . '/header.php'; ?> @@ -483,6 +618,13 @@ require __DIR__ . '/header.php'; align-items: center; } +.ephemeriden-results-actions { + display: flex; + align-items: center; + gap: 0.65rem; + flex-wrap: wrap; +} + .ephemeriden-message { margin-bottom: 0.9rem; padding: 0.85rem 1rem; @@ -525,6 +667,55 @@ require __DIR__ . '/header.php'; white-space: normal; } +.ephemeriden-chart-card[hidden] { + display: none; +} + +.ephemeriden-chart-head { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 1rem; + flex-wrap: wrap; + margin-bottom: 0.9rem; +} + +.ephemeriden-chart-note { + color: var(--text-dim); + font-size: 0.92rem; +} + +.ephemeriden-chart-controls { + display: flex; + align-items: end; + gap: 0.8rem; + flex-wrap: wrap; +} + +.ephemeriden-chart-controls .form-group { + min-width: 220px; + margin: 0; +} + +.ephemeriden-chart-stage { + display: flex; + justify-content: center; + width: 100%; +} + +.ephemeriden-chart-stage canvas { + width: 100%; + max-width: 760px; + height: auto; + display: block; +} + +.ephemeriden-chart-legend { + margin-top: 0.9rem; + color: var(--text-dim); + font-size: 0.9rem; +} + @media (max-width: 900px) { .ephemeriden-fields-row { grid-template-columns: repeat(2, minmax(180px, 1fr)); @@ -582,7 +773,7 @@ require __DIR__ . '/header.php'; id="ephemeridenRangeValue" name="range_value" min="1" - max="31" + max="100" step="1" value="" inputmode="numeric" @@ -615,14 +806,17 @@ require __DIR__ . '/header.php'; -
- - - - - - -
+
+ +
+ + + + + + +
+
@@ -670,4 +864,672 @@ require __DIR__ . '/header.php'; + + + + + + + + + diff --git a/public/py/ephemeriden_api.py b/public/py/ephemeriden_api.py index 2411c8c..7234cf7 100644 --- a/public/py/ephemeriden_api.py +++ b/public/py/ephemeriden_api.py @@ -347,6 +347,14 @@ def custom_altitude_deg(body_type: str, payload: dict, observer: astronomy.Obser return float(horizontal.lat) +def custom_horizontal_coordinates(body_type: str, payload: dict, observer: astronomy.Observer, time_value: astronomy.Time) -> tuple[float, float]: + topocentric_vector = custom_topocentric_vector(body_type, payload, observer, time_value) + rotation = astronomy.Rotation_EQJ_HOR(time_value, observer) + horizontal_vector = astronomy.RotateVector(rotation, topocentric_vector) + horizontal = astronomy.HorizonFromVector(horizontal_vector, astronomy.Refraction.Normal) + return float(horizontal.lon), float(horizontal.lat) + + def refine_custom_event( body_type: str, payload: dict, @@ -509,14 +517,18 @@ def action_planet_ephemeris(args: list[str]) -> dict: def row_builder(current_local: datetime, time_value: astronomy.Time, rise_label: str | None, set_label: str | None) -> dict: eq = astronomy.Equator(body, time_value, observer, False, True) + horizontal = astronomy.Horizon(time_value, observer, float(eq.ra), float(eq.dec), astronomy.Refraction.Normal) return { "object_name": label, "date_local": current_local.strftime("%d.%m.%Y"), "time_local": current_local.strftime("%H:%M"), + "local_iso": current_local.isoformat(), "ra": format_ra_hours(float(eq.ra)), "ra_decimal_hours": round(float(eq.ra), 8), "dec": format_dec_deg(float(eq.dec)), "dec_decimal_deg": round(float(eq.dec), 8), + "azimuth_deg": round(float(horizontal.azimuth), 6), + "altitude_deg": round(float(horizontal.altitude), 6), "rise": rise_label, "set": set_label, } @@ -595,6 +607,7 @@ def action_small_body_ephemeris(args: list[str]) -> dict: def row_builder(current_local: datetime, time_value: astronomy.Time, rise_label: str | None, set_label: str | None) -> dict: try: topocentric_vector = custom_topocentric_vector(body_type, payload, observer, time_value) + azimuth_deg, altitude_deg = custom_horizontal_coordinates(body_type, payload, observer, time_value) except Exception as exc: fail("Die Ephemeriden konnten fuer dieses Objekt nicht berechnet werden.", extra={"details": str(exc), "object": object_label}) @@ -603,10 +616,13 @@ def action_small_body_ephemeris(args: list[str]) -> dict: "object_name": object_label, "date_local": current_local.strftime("%d.%m.%Y"), "time_local": current_local.strftime("%H:%M"), + "local_iso": current_local.isoformat(), "ra": format_ra_hours(float(eq.ra)), "ra_decimal_hours": round(float(eq.ra), 8), "dec": format_dec_deg(float(eq.dec)), "dec_decimal_deg": round(float(eq.dec), 8), + "azimuth_deg": round(float(azimuth_deg), 6), + "altitude_deg": round(float(altitude_deg), 6), "rise": rise_label, "set": set_label, }