ISS in die Monatsvorhersage eingebaut
This commit is contained in:
@@ -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.
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user