ISS in die Monatsvorhersage eingebaut

This commit is contained in:
Eskimue
2026-04-04 14:16:10 +02:00
parent 5cc3f57b1c
commit 08de43621e
8 changed files with 852 additions and 7 deletions
+287 -7
View File
@@ -54,6 +54,31 @@ $planetConjunctionMaxSeparationDeg = 1.0;
// beruecksichtigt.
$moonOccultationMaxMag = 2.0;
// -------------------------------------------------------------------------
// Schwellen fuer "Gute Satelliten-Ueberfluege"
// -------------------------------------------------------------------------
// Es wird nur der Favoriten-Satellit des angemeldeten Users geprueft.
//
// Ein Ueberflug zaehlt als guter Beobachtungstermin nur dann, wenn er am
// Hoehepunkt mindestens diese Hoehe ueber dem Horizont erreicht.
$favoriteSatelliteBestPassMinAltitudeDeg = 80.0;
// Gleichzeitig muss die Sonne zum Maximum des Ueberflugs mindestens so weit
// unter dem Horizont stehen. -12 Grad entspricht dem Beginn/Ende der
// nautischen Daemmerung und sorgt fuer ausreichend dunklen Himmel.
$favoriteSatelliteBestPassMaxSunAltitudeDeg = -14.0;
// Optional koennen wir die Liste auf die erste Nachthaelfte begrenzen.
// "Erste Nachthaelfte" ist hier bewusst pragmatisch als lokaler Abend vor
// Mitternacht definiert. Ueberfluege nach 00:00 Ortszeit werden damit
// ausgefiltert.
$favoriteSatelliteBestPassOnlyFirstNightHalf = true;
// Optional koennen wir nur besonders helle Ueberfluege anzeigen.
// Bei Magnituden gilt: kleinere Werte sind heller. 0.0 mag bedeutet also,
// dass nur Ueberfluege mit Peak-Helligkeit von 0 mag oder heller bleiben.
$favoriteSatelliteBestPassMaxPeakMagnitude = 0.0;
// -------------------------------------------------------------------------
// Schwellen fuer "Gute Merkur-Sichtbarkeit"
// -------------------------------------------------------------------------
@@ -160,6 +185,9 @@ $outerPlanetStationEvents = [];
$jupiterMoonSideEvents = [];
$moonOccultations = [];
$meteorShowerPeaks = [];
$favoriteSatellitePasses = [];
$favoriteSatelliteName = null;
$favoriteSatellitePayload = [];
$seasonChanges = [];
$timeChanges = [];
$monthEventList = [];
@@ -230,6 +258,133 @@ if (!function_exists('monthForecastRadiantIsVisible')) {
}
}
if (!function_exists('monthForecastTleChecksum')) {
function monthForecastTleChecksum(string $line): int
{
$sum = 0;
for ($i = 0; $i < 68; $i++) {
$c = $line[$i] ?? ' ';
if (ctype_digit($c)) {
$sum += (int) $c;
} elseif ($c === '-') {
$sum += 1;
}
}
return $sum % 10;
}
}
if (!function_exists('monthForecastTleExp')) {
function monthForecastTleExp(float $value): string
{
if (abs($value) < 1e-99) {
return ' 00000-0';
}
$sign = $value >= 0 ? '+' : '-';
$abs = abs($value);
$exp = (int) floor(log10($abs)) + 1;
$mantissa = (int) round($abs / pow(10, $exp) * 1e5);
if ($mantissa >= 100000) {
$mantissa = 10000;
$exp++;
}
$expSign = $exp >= 0 ? '+' : '-';
return sprintf('%s%05d%s%d', $sign, $mantissa, $expSign, abs($exp));
}
}
if (!function_exists('monthForecastTleMmDot')) {
function monthForecastTleMmDot(float $value): string
{
$sign = $value >= 0 ? ' ' : '-';
return sprintf('%s.%08d', $sign, (int) round(abs($value) * 1e8));
}
}
if (!function_exists('monthForecastTleEpoch')) {
function monthForecastTleEpoch(string $epochStr): string
{
$dt = new DateTimeImmutable($epochStr, new DateTimeZone('UTC'));
$yy = (int) $dt->format('Y') % 100;
$jan1 = new DateTimeImmutable($dt->format('Y') . '-01-01 00:00:00', new DateTimeZone('UTC'));
$secs = (float) $dt->format('U') - (float) $jan1->format('U');
$day = $secs / 86400.0 + 1.0;
return sprintf('%02d%012.8f', $yy, $day);
}
}
if (!function_exists('monthForecastTleIntlDesig')) {
function monthForecastTleIntlDesig(?string $objectId): string
{
if (!$objectId) {
return str_repeat(' ', 8);
}
if (preg_match('/^(\d{4})-(\d+)([A-Z]*)$/i', trim($objectId), $match)) {
$yy = substr($match[1], 2, 2);
$launch = str_pad($match[2], 3, '0', STR_PAD_LEFT);
$piece = str_pad(strtoupper($match[3]), 3, ' ', STR_PAD_RIGHT);
return substr(str_pad($yy . $launch . $piece, 8, ' '), 0, 8);
}
return str_pad(substr($objectId, 0, 8), 8);
}
}
if (!function_exists('monthForecastBuildTleLine1')) {
function monthForecastBuildTleLine1(array $sat, array $tle): string
{
$norad = (int) ($sat['norad_cat_id'] ?? 0);
$class = in_array($sat['classification_type'] ?? '', ['U', 'C', 'S'], true)
? (string) $sat['classification_type']
: 'U';
$line = sprintf(
'1 %05d%1s %-8s %14s %10s %8s %8s %1d %4d',
$norad,
$class,
monthForecastTleIntlDesig($sat['object_id'] ?? null),
monthForecastTleEpoch((string) ($tle['epoch'] ?? '')),
monthForecastTleMmDot((float) ($tle['mean_motion_dot'] ?? 0)),
monthForecastTleExp((float) ($tle['mean_motion_ddot'] ?? 0)),
monthForecastTleExp((float) ($tle['bstar'] ?? 0)),
(int) ($tle['ephemeris_type'] ?? 0),
(int) ($tle['element_set_no'] ?? 0)
);
$line = str_pad($line, 68);
return $line . monthForecastTleChecksum($line);
}
}
if (!function_exists('monthForecastBuildTleLine2')) {
function monthForecastBuildTleLine2(array $sat, array $tle): string
{
$norad = (int) ($sat['norad_cat_id'] ?? 0);
$ecc = (float) ($tle['eccentricity'] ?? 0);
$eccStr = substr(sprintf('%.7f', $ecc), 2);
$line = sprintf(
'2 %05d %8.4f %8.4f %7s %8.4f %8.4f %11.8f%5d',
$norad,
(float) ($tle['inclination'] ?? 0),
(float) ($tle['ra_of_asc_node'] ?? 0),
$eccStr,
(float) ($tle['arg_of_pericenter'] ?? 0),
(float) ($tle['mean_anomaly'] ?? 0),
(float) ($tle['mean_motion'] ?? 0),
(int) ($tle['rev_at_epoch'] ?? 0)
);
$line = str_pad($line, 68);
return $line . monthForecastTleChecksum($line);
}
}
if (!function_exists('findMonthForecastSolarLongitudeCrossing')) {
function findMonthForecastSolarLongitudeCrossing(
DateTimeImmutable $rangeStartLocal,
@@ -610,6 +765,44 @@ try {
'zhr_variable' => trim((string) ($row['zhr_variable'] ?? '')),
];
}
if ($loggedIn && isset($_SESSION['user_id'])) {
$favoriteSatelliteStmt = $pdo->prepare(
"SELECT s.id, s.norad_cat_id, s.object_name, s.object_id, s.classification_type,
s.std_mag, s.rcs_size, s.rcs,
t.epoch, t.mean_motion, t.eccentricity, t.inclination,
t.ra_of_asc_node, t.arg_of_pericenter, t.mean_anomaly,
t.ephemeris_type, t.element_set_no, t.rev_at_epoch,
t.bstar, t.mean_motion_dot, t.mean_motion_ddot
FROM app_user_satellites us
INNER JOIN sat_satellites s ON s.id = us.satellite_id
INNER JOIN sat_tle_current t ON t.satellite_id = s.id
WHERE us.user_id = :user_id
AND us.is_favorite = 1
AND t.epoch IS NOT NULL
AND t.mean_motion IS NOT NULL
ORDER BY s.object_name ASC
LIMIT 1"
);
$favoriteSatelliteStmt->execute([
':user_id' => (int) $_SESSION['user_id'],
]);
$favoriteSatelliteRow = $favoriteSatelliteStmt->fetch();
if (is_array($favoriteSatelliteRow)) {
$favoriteSatelliteName = trim((string) ($favoriteSatelliteRow['object_name'] ?? ''));
$favoriteSatellitePayload = [[
'id' => (int) ($favoriteSatelliteRow['id'] ?? 0),
'norad_cat_id' => (int) ($favoriteSatelliteRow['norad_cat_id'] ?? 0),
'object_name' => $favoriteSatelliteName,
'is_favorite' => true,
'std_mag' => $favoriteSatelliteRow['std_mag'] !== null ? (float) $favoriteSatelliteRow['std_mag'] : null,
'rcs_size' => $favoriteSatelliteRow['rcs_size'] !== null ? (string) $favoriteSatelliteRow['rcs_size'] : null,
'rcs' => $favoriteSatelliteRow['rcs'] !== null && (float) $favoriteSatelliteRow['rcs'] > 0 ? (float) $favoriteSatelliteRow['rcs'] : null,
'tle_line1' => monthForecastBuildTleLine1($favoriteSatelliteRow, $favoriteSatelliteRow),
'tle_line2' => monthForecastBuildTleLine2($favoriteSatelliteRow, $favoriteSatelliteRow),
]];
}
}
} catch (Throwable $e) {
$starDataError = $e->getMessage();
}
@@ -628,6 +821,11 @@ $chartLocalTime = new DateTimeImmutable(
new DateTimeZone($chartTimezone)
);
$chartUtcTime = $chartLocalTime->setTimezone(new DateTimeZone('UTC'));
$monthStartLocal = new DateTimeImmutable(
sprintf('%04d-%02d-01 00:00:00', $selectedYear, $selectedMonth),
new DateTimeZone($chartTimezone)
);
$monthEndLocal = $monthStartLocal->modify('first day of next month');
$pythonExecutable = PHP_OS_FAMILY === 'Windows' ? 'python' : '/usr/bin/python';
$pythonScriptPath = __DIR__ . '/py/api.py';
@@ -682,6 +880,34 @@ if (is_array($pythonDecoded) && ($pythonDecoded['ok'] ?? false)) {
$planetErrorText = $pythonRaw;
}
if (!empty($favoriteSatellitePayload)) {
$favoriteSatellitePassOutput = [];
$favoriteSatellitePassArgs = [
$pythonScriptPath,
'satellite_passes',
(string) $chartLatitude,
(string) $chartLongitude,
(string) $chartElevation,
$chartTimezone,
$monthStartLocal->setTimezone(new DateTimeZone('UTC'))->format('Y-m-d\TH:i:s\Z'),
$monthEndLocal->setTimezone(new DateTimeZone('UTC'))->format('Y-m-d\TH:i:s\Z'),
base64_encode((string) json_encode($favoriteSatellitePayload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)),
];
$favoriteSatellitePassCommand = $pythonExecutable . ' ' . implode(' ', array_map('escapeshellarg', $favoriteSatellitePassArgs)) . ' 2>&1';
exec($favoriteSatellitePassCommand, $favoriteSatellitePassOutput);
$favoriteSatellitePassRaw = trim(implode("\n", $favoriteSatellitePassOutput));
$favoriteSatellitePassDecoded = json_decode($favoriteSatellitePassRaw, true);
if (is_array($favoriteSatellitePassDecoded) && ($favoriteSatellitePassDecoded['ok'] ?? false)) {
$favoriteSatelliteResults = is_array($favoriteSatellitePassDecoded['results'] ?? null)
? $favoriteSatellitePassDecoded['results']
: [];
$favoriteSatellitePasses = is_array($favoriteSatelliteResults[0]['passes'] ?? null)
? $favoriteSatelliteResults[0]['passes']
: [];
}
}
$moonPhaseOutput = [];
$moonPhaseExitCode = 0;
$moonPhaseArgs = [
@@ -1465,21 +1691,22 @@ foreach ($moonOccultations as $occultationEvent) {
$starLabel = (string) ($star['label'] ?? 'Stern');
$starMag = isset($star['mag']) ? number_format((float) $star['mag'], 1, ',', '') : '';
$maximumLocalDate = (string) ($maximum['local_date'] ?? '');
if ($maximumLocalDate !== '') {
$parsedMaximumDate = DateTimeImmutable::createFromFormat('Y-m-d', $maximumLocalDate);
if ($parsedMaximumDate instanceof DateTimeImmutable) {
$maximumLocalDate = $parsedMaximumDate->format('d.m.Y');
}
}
$monthEventList[] = [
'event' => 'Mond bedeckt ' . $starLabel . ($starMag !== '' ? ' (' . $starMag . ' mag)' : ''),
'date' => (string) ($maximum['local_date'] ?? ''),
'date' => $maximumLocalDate,
'time' => (string) ($maximum['local_time'] ?? ''),
'local_iso' => (string) ($maximum['local_iso'] ?? ''),
'type' => 'moon_occultation',
];
}
$monthStartLocal = new DateTimeImmutable(
sprintf('%04d-%02d-01 00:00:00', $selectedYear, $selectedMonth),
new DateTimeZone($chartTimezone)
);
$monthEndLocal = $monthStartLocal->modify('first day of next month');
foreach ($meteorShowerPeaks as $meteorShowerPeak) {
if (!monthForecastRadiantIsVisible($chartLatitude, $meteorShowerPeak['radiant_delta_deg'] ?? null)) {
continue;
@@ -1531,6 +1758,59 @@ foreach ($meteorShowerPeaks as $meteorShowerPeak) {
}
foreach ($favoriteSatellitePasses as $satellitePass) {
if (!is_array($satellitePass)) {
continue;
}
$peakUtc = trim((string) ($satellitePass['peak_utc'] ?? ''));
if ($peakUtc === '') {
continue;
}
$peakAltitude = isset($satellitePass['peak_el']) ? (float) $satellitePass['peak_el'] : null;
$peakSunAltitude = isset($satellitePass['peak_sun_altitude_deg']) ? (float) $satellitePass['peak_sun_altitude_deg'] : null;
$peakMagnitude = isset($satellitePass['peak_mag']['mag']) ? (float) $satellitePass['peak_mag']['mag'] : null;
if ($peakAltitude === null || $peakSunAltitude === null) {
continue;
}
if ($peakAltitude < $favoriteSatelliteBestPassMinAltitudeDeg) {
continue;
}
if ($peakSunAltitude > $favoriteSatelliteBestPassMaxSunAltitudeDeg) {
continue;
}
try {
$peakDateTime = (new DateTimeImmutable($peakUtc))->setTimezone(new DateTimeZone($chartTimezone));
} catch (Throwable) {
continue;
}
if ($favoriteSatelliteBestPassOnlyFirstNightHalf && (int) $peakDateTime->format('G') < 12) {
continue;
}
if ($peakMagnitude === null || $peakMagnitude > $favoriteSatelliteBestPassMaxPeakMagnitude) {
continue;
}
$peakAltitudeText = number_format($peakAltitude, 0, ',', '');
$eventLabel = 'Guter Ueberflug ' . ($favoriteSatelliteName !== null && $favoriteSatelliteName !== '' ? $favoriteSatelliteName : 'Favoriten-Satellit');
$peakMagnitudeText = number_format($peakMagnitude, 1, ',', '');
$eventLabel .= ' (max. ' . $peakAltitudeText . ' Grad, ' . $peakMagnitudeText . ' mag)';
$monthEventList[] = [
'event' => $eventLabel,
'date' => $peakDateTime->format('d.m.Y'),
'time' => $peakDateTime->format('H:i'),
'local_iso' => $peakDateTime->format(DateTimeInterface::ATOM),
'type' => 'favorite_satellite_pass',
];
}
usort(
$monthEventList,
static fn (array $a, array $b): int => strcmp((string) ($a['local_iso'] ?? ''), (string) ($b['local_iso'] ?? ''))
Binary file not shown.
+7
View File
@@ -3941,6 +3941,7 @@ def action_satellite_passes(args: list[str]) -> dict:
# Magnitude at peak
peak_mag = None
peak_sun_altitude_deg = None
if peak_time:
jd5, fr5 = _jday_from_dt(peak_time)
e5, r5, _ = satrec.sgp4(jd5, fr5)
@@ -3953,6 +3954,11 @@ def action_satellite_passes(args: list[str]) -> dict:
r5, sun5, obs_ecef, lat_rad, lon_rad,
_gmst_rad(jd5 + fr5)
)
peak_sun_altitude_deg = body_altitude_deg(
astronomy.Body.Sun,
astronomy.Observer(latitude, longitude, elevation_m),
peak_time,
)
duration_s = int(round((los_time - aos_time).total_seconds()))
local_tz = tz
@@ -3970,6 +3976,7 @@ def action_satellite_passes(args: list[str]) -> dict:
"los_az": round(los_az, 2) if los_az is not None else None,
"peak_az": round(peak_az, 2) if peak_az is not None else None,
"peak_el": round(peak_el, 2),
"peak_sun_altitude_deg": round(float(peak_sun_altitude_deg), 2) if peak_sun_altitude_deg is not None else None,
"duration_s": duration_s,
"path": path,
"shadow_entry": shadow_entry,