Files

226 lines
6.7 KiB
PHP

<?php
declare(strict_types=1);
session_start();
header('Content-Type: application/json; charset=utf-8');
header('Cache-Control: no-store');
function tleChecksum(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;
}
function tleExp(float $val): string {
if (abs($val) < 1e-99) {
return ' 00000-0';
}
$sign = $val >= 0 ? '+' : '-';
$abs = abs($val);
$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));
}
function tleMmDot(float $val): string {
$sign = $val >= 0 ? ' ' : '-';
return sprintf('%s.%08d', $sign, (int)round(abs($val) * 1e8));
}
function tleEpoch(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);
}
function tleIntlDesig(?string $objectId): string {
if (!$objectId) {
return str_repeat(' ', 8);
}
if (preg_match('/^(\d{4})-(\d+)([A-Z]*)$/i', trim($objectId), $m)) {
$yy = substr($m[1], 2, 2);
$launch = str_pad($m[2], 3, '0', STR_PAD_LEFT);
$piece = str_pad(strtoupper($m[3]), 3, ' ', STR_PAD_RIGHT);
return substr(str_pad($yy . $launch . $piece, 8, ' '), 0, 8);
}
return str_pad(substr($objectId, 0, 8), 8);
}
function buildLine1(array $sat, array $tle): string {
$norad = (int)$sat['norad_cat_id'];
$class = in_array($sat['classification_type'] ?? '', ['U', 'C', 'S'], true)
? $sat['classification_type']
: 'U';
$line = sprintf(
'1 %05d%1s %-8s %14s %10s %8s %8s %1d %4d',
$norad,
$class,
tleIntlDesig($sat['object_id'] ?? null),
tleEpoch((string)$tle['epoch']),
tleMmDot((float)($tle['mean_motion_dot'] ?? 0)),
tleExp((float)($tle['mean_motion_ddot'] ?? 0)),
tleExp((float)($tle['bstar'] ?? 0)),
(int)($tle['ephemeris_type'] ?? 0),
(int)($tle['element_set_no'] ?? 0)
);
$line = str_pad($line, 68);
return $line . tleChecksum($line);
}
function buildLine2(array $sat, array $tle): string {
$norad = (int)$sat['norad_cat_id'];
$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 . tleChecksum($line);
}
$userId = isset($_SESSION['user_id']) ? (int)$_SESSION['user_id'] : 0;
if ($userId <= 0) {
http_response_code(401);
echo json_encode(['error' => 'Nicht angemeldet.']);
exit;
}
$requestedIds = array_values(array_filter(
array_map('intval', explode(',', (string)($_GET['ids'] ?? ''))),
static fn(int $id): bool => $id > 0
));
$cfg = require __DIR__ . '/../../config/database.php';
try {
$dsn = sprintf(
'mysql:host=%s;dbname=%s;charset=%s',
$cfg['host'],
$cfg['dbname'],
$cfg['charset'] ?? 'utf8mb4'
);
$pdo = new PDO($dsn, $cfg['user'], $cfg['pass'], [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);
} catch (PDOException $e) {
http_response_code(500);
echo json_encode(['error' => 'DB-Verbindung fehlgeschlagen.']);
exit;
}
$sql = "
SELECT
s.id,
s.norad_cat_id,
s.object_name,
s.object_id,
s.classification_type,
us.is_favorite,
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 t.epoch IS NOT NULL
AND t.mean_motion IS NOT NULL
";
$params = [':user_id' => $userId];
if ($requestedIds !== []) {
$idPlaceholders = [];
foreach ($requestedIds as $index => $noradId) {
$placeholder = ':norad_' . $index;
$idPlaceholders[] = $placeholder;
$params[$placeholder] = $noradId;
}
$sql .= ' AND s.norad_cat_id IN (' . implode(', ', $idPlaceholders) . ')';
}
$sql .= ' ORDER BY us.is_favorite DESC, s.object_name ASC';
try {
$stmt = $pdo->prepare($sql);
$stmt->execute($params);
$rows = $stmt->fetchAll();
} catch (PDOException $e) {
http_response_code(500);
echo json_encode(['error' => 'Abfrage fehlgeschlagen.']);
exit;
}
$result = [];
foreach ($rows as $row) {
$result[] = [
'id' => (int)$row['id'],
'norad_cat_id' => (int)$row['norad_cat_id'],
'object_name' => (string)$row['object_name'],
'object_id' => $row['object_id'] !== null ? (string)$row['object_id'] : null,
'classification_type' => $row['classification_type'] !== null ? (string)$row['classification_type'] : null,
'is_favorite' => (int)($row['is_favorite'] ?? 0) === 1,
'epoch' => (string)$row['epoch'],
'mean_motion' => (float)$row['mean_motion'],
'eccentricity' => (float)$row['eccentricity'],
'inclination' => (float)$row['inclination'],
'ra_of_asc_node' => (float)$row['ra_of_asc_node'],
'arg_of_pericenter' => (float)$row['arg_of_pericenter'],
'mean_anomaly' => (float)$row['mean_anomaly'],
'ephemeris_type' => (int)$row['ephemeris_type'],
'element_set_no' => (int)$row['element_set_no'],
'rev_at_epoch' => (int)$row['rev_at_epoch'],
'bstar' => (float)$row['bstar'],
'mean_motion_dot' => (float)$row['mean_motion_dot'],
'mean_motion_ddot' => (float)$row['mean_motion_ddot'],
'tle_line1' => buildLine1($row, $row),
'tle_line2' => buildLine2($row, $row),
];
}
echo json_encode($result, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);