Jupitersystem GRF verbessert
This commit is contained in:
+30
-4
@@ -1,6 +1,32 @@
|
|||||||
{
|
{
|
||||||
"refDate": "2026-04-13T00:31:40.897Z",
|
"refDate": "2026-04-13T04:07:00.000Z",
|
||||||
"refAngle": 312227.6287687423,
|
"refAngle": 312229.88248732366,
|
||||||
"calibratedAt": "2026-04-12T09:26:52+00:00",
|
"calibratedAt": "2026-04-12T09:35:41+00:00",
|
||||||
"calibratedBy": "Eskimue"
|
"calibratedBy": "Eskimue",
|
||||||
|
"history": [
|
||||||
|
{
|
||||||
|
"refDate": "2025-04-12T01:08:00.000Z",
|
||||||
|
"refAngle": 306668.75732167566,
|
||||||
|
"calibratedAt": "2026-04-12T09:33:58+00:00",
|
||||||
|
"calibratedBy": "Eskimue"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"refDate": "2026-01-01T09:49:00.000Z",
|
||||||
|
"refAngle": 310684.2913082397,
|
||||||
|
"calibratedAt": "2026-04-12T09:38:07+00:00",
|
||||||
|
"calibratedBy": "Eskimue"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"refDate": "2026-04-12T08:15:00.000Z",
|
||||||
|
"refAngle": 312217.3093069398,
|
||||||
|
"calibratedAt": "2026-04-12T09:32:29+00:00",
|
||||||
|
"calibratedBy": "Eskimue"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"refDate": "2026-04-13T04:07:00.000Z",
|
||||||
|
"refAngle": 312229.88248732366,
|
||||||
|
"calibratedAt": "2026-04-12T09:35:41+00:00",
|
||||||
|
"calibratedBy": "Eskimue"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
@@ -24,12 +24,26 @@ if (isset($_GET['grs_api'])) {
|
|||||||
if (!$isAdmin) { http_response_code(403); echo json_encode(['error' => 'Nicht autorisiert']); exit; }
|
if (!$isAdmin) { http_response_code(403); echo json_encode(['error' => 'Nicht autorisiert']); exit; }
|
||||||
$body = json_decode(file_get_contents('php://input'), true);
|
$body = json_decode(file_get_contents('php://input'), true);
|
||||||
if (!$body || !isset($body['refDate'], $body['refAngle'])) { http_response_code(400); echo json_encode(['error' => 'Ungültige Daten']); exit; }
|
if (!$body || !isset($body['refDate'], $body['refAngle'])) { http_response_code(400); echo json_encode(['error' => 'Ungültige Daten']); exit; }
|
||||||
$data = [
|
$existing = [];
|
||||||
|
if (file_exists($dataFile)) {
|
||||||
|
$existing = json_decode(file_get_contents($dataFile), true) ?? [];
|
||||||
|
}
|
||||||
|
$history = $existing['history'] ?? [];
|
||||||
|
$history[] = [
|
||||||
'refDate' => (string) $body['refDate'],
|
'refDate' => (string) $body['refDate'],
|
||||||
'refAngle' => (float) $body['refAngle'],
|
'refAngle' => (float) $body['refAngle'],
|
||||||
'calibratedAt' => date('c'),
|
'calibratedAt' => date('c'),
|
||||||
'calibratedBy' => $_SESSION['username'] ?? 'admin',
|
'calibratedBy' => $_SESSION['username'] ?? 'admin',
|
||||||
];
|
];
|
||||||
|
usort($history, fn($a, $b) => strcmp((string) $a['refDate'], (string) $b['refDate']));
|
||||||
|
$latest = end($history);
|
||||||
|
$data = [
|
||||||
|
'refDate' => $latest['refDate'],
|
||||||
|
'refAngle' => $latest['refAngle'],
|
||||||
|
'calibratedAt' => $latest['calibratedAt'],
|
||||||
|
'calibratedBy' => $latest['calibratedBy'],
|
||||||
|
'history' => $history,
|
||||||
|
];
|
||||||
$written = file_put_contents($dataFile, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
|
$written = file_put_contents($dataFile, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
|
||||||
echo $written !== false ? json_encode(['ok' => true]) : json_encode(['error' => 'Schreibfehler – Dateiberechtigung prüfen']);
|
echo $written !== false ? json_encode(['ok' => true]) : json_encode(['error' => 'Schreibfehler – Dateiberechtigung prüfen']);
|
||||||
exit;
|
exit;
|
||||||
@@ -140,6 +154,15 @@ $bodyClass = 'solarsystem-page jupitersystem-page';
|
|||||||
<button class="btn btn-secondary btn-full mt-md" id="grsBtnShadowRays">Schattenstrahlen anzeigen</button>
|
<button class="btn btn-secondary btn-full mt-md" id="grsBtnShadowRays">Schattenstrahlen anzeigen</button>
|
||||||
<button class="btn btn-primary btn-full mt-xs" id="grsBtnSave">Kalibrierung speichern</button>
|
<button class="btn btn-primary btn-full mt-xs" id="grsBtnSave">Kalibrierung speichern</button>
|
||||||
<div id="grsCalibStatus" class="hint inline-fade mt-sm"></div>
|
<div id="grsCalibStatus" class="hint inline-fade mt-sm"></div>
|
||||||
|
|
||||||
|
<div id="grsDriftSection" style="display:none">
|
||||||
|
<div class="solarsystem-label mt-md">GRS Drift-Historie</div>
|
||||||
|
<p class="hint mt-2xs">Abweichung vom System-II-Rhythmus zwischen je zwei Abgleichen.</p>
|
||||||
|
<div id="grsDriftAvg" class="hint mt-2xs" style="color:var(--gold-pale)"></div>
|
||||||
|
<div style="overflow-x:auto;margin-top:0.5rem">
|
||||||
|
<table id="grsDriftTable" class="data-table" style="font-size:0.8rem;white-space:nowrap"></table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
</div>
|
</div>
|
||||||
@@ -779,6 +802,60 @@ function parseGrsTransitInput(str) {
|
|||||||
return isNaN(d.getTime()) ? null : d;
|
return isNaN(d.getTime()) ? null : d;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderGrsDrift(history) {
|
||||||
|
const section = document.getElementById('grsDriftSection');
|
||||||
|
const table = document.getElementById('grsDriftTable');
|
||||||
|
const avgEl = document.getElementById('grsDriftAvg');
|
||||||
|
if (!section || !table || !avgEl || !Array.isArray(history) || history.length < 2) {
|
||||||
|
if (section) section.style.display = 'none';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const RAD_TO_DEG = 180 / Math.PI;
|
||||||
|
|
||||||
|
let rows = '';
|
||||||
|
const driftRates = [];
|
||||||
|
|
||||||
|
for (let i = 1; i < history.length; i++) {
|
||||||
|
const prev = history[i - 1];
|
||||||
|
const curr = history[i];
|
||||||
|
const t1 = new Date(prev.refDate).getTime();
|
||||||
|
const t2 = new Date(curr.refDate).getTime();
|
||||||
|
const elapsedDays = (t2 - t1) / 86400000;
|
||||||
|
if (elapsedDays <= 0) continue;
|
||||||
|
|
||||||
|
const expectedDeg = (elapsedDays / GRS_SYSTEM_II_PERIOD_DAYS) * 360;
|
||||||
|
const actualDeg = (curr.refAngle - prev.refAngle) * RAD_TO_DEG;
|
||||||
|
// Drift auf den Bereich [-180, 180] normieren
|
||||||
|
let drift = ((actualDeg - expectedDeg) % 360 + 540) % 360 - 180;
|
||||||
|
const driftPerDay = drift / elapsedDays;
|
||||||
|
driftRates.push(driftPerDay);
|
||||||
|
|
||||||
|
const sign = driftPerDay >= 0 ? '+' : '';
|
||||||
|
const color = Math.abs(driftPerDay) < 0.05 ? 'var(--text-dim)' : (driftPerDay > 0 ? 'var(--gold-pale)' : 'var(--cyan)');
|
||||||
|
const dateStr = new Date(curr.refDate).toLocaleString('de-DE', { timeZone: 'UTC', dateStyle: 'short', timeStyle: 'short' });
|
||||||
|
|
||||||
|
rows += `<tr>
|
||||||
|
<td>${dateStr} UT</td>
|
||||||
|
<td>${elapsedDays.toFixed(1)} d</td>
|
||||||
|
<td style="color:${color};font-variant-numeric:tabular-nums">${sign}${drift.toFixed(2)}°</td>
|
||||||
|
<td style="color:${color};font-variant-numeric:tabular-nums">${sign}${driftPerDay.toFixed(3)}°/d</td>
|
||||||
|
</tr>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.innerHTML = `<thead><tr>
|
||||||
|
<th>Abgleich</th><th>Abstand</th><th>Drift ges.</th><th>Drift/Tag</th>
|
||||||
|
</tr></thead><tbody>${rows}</tbody>`;
|
||||||
|
|
||||||
|
if (driftRates.length > 0) {
|
||||||
|
const avg = driftRates.reduce((s, v) => s + v, 0) / driftRates.length;
|
||||||
|
const sign = avg >= 0 ? '+' : '';
|
||||||
|
avgEl.textContent = `Ø Drift: ${sign}${avg.toFixed(3)}°/Tag`;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.style.display = '';
|
||||||
|
}
|
||||||
|
|
||||||
function formatGrsStatus(data) {
|
function formatGrsStatus(data) {
|
||||||
if (!data.calibratedAt) return 'Noch keine Kalibrierung gespeichert.';
|
if (!data.calibratedAt) return 'Noch keine Kalibrierung gespeichert.';
|
||||||
const at = new Date(data.calibratedAt).toLocaleString('de-DE', { timeZone: 'UTC', dateStyle: 'short', timeStyle: 'short' });
|
const at = new Date(data.calibratedAt).toLocaleString('de-DE', { timeZone: 'UTC', dateStyle: 'short', timeStyle: 'short' });
|
||||||
@@ -797,6 +874,7 @@ async function loadGrsCalibration() {
|
|||||||
}
|
}
|
||||||
const statusEl = document.getElementById('grsCalibStatus');
|
const statusEl = document.getElementById('grsCalibStatus');
|
||||||
if (statusEl) statusEl.textContent = formatGrsStatus(data);
|
if (statusEl) statusEl.textContent = formatGrsStatus(data);
|
||||||
|
renderGrsDrift(data.history);
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -948,6 +1026,12 @@ function attachAdminHandlers() {
|
|||||||
grsBtnRot5R.addEventListener('click', () => shiftGrsRotation(5));
|
grsBtnRot5R.addEventListener('click', () => shiftGrsRotation(5));
|
||||||
|
|
||||||
grsBtnSave.addEventListener('click', async () => {
|
grsBtnSave.addEventListener('click', async () => {
|
||||||
|
if (grsTransitInput.value.trim() === '') {
|
||||||
|
grsParseError.textContent = 'Bitte zuerst einen GRS-Transitzeitpunkt eingeben und auf „Auf diese Zeit setzen" klicken.';
|
||||||
|
grsParseError.classList.remove('is-hidden');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const elapsedDays = (simDate - grsRefDate) / 86400000;
|
const elapsedDays = (simDate - grsRefDate) / 86400000;
|
||||||
grsRefAngle = grsRefAngle + (elapsedDays / GRS_SYSTEM_II_PERIOD_DAYS) * Math.PI * 2;
|
grsRefAngle = grsRefAngle + (elapsedDays / GRS_SYSTEM_II_PERIOD_DAYS) * Math.PI * 2;
|
||||||
grsRefDate = new Date(simDate);
|
grsRefDate = new Date(simDate);
|
||||||
@@ -956,6 +1040,10 @@ function attachAdminHandlers() {
|
|||||||
grsCalibStatus.textContent = ok === true
|
grsCalibStatus.textContent = ok === true
|
||||||
? `Gespeichert: ${simDate.toISOString().slice(0, 16).replace('T', ' ')} UT\nFür alle Nutzer aktiv.`
|
? `Gespeichert: ${simDate.toISOString().slice(0, 16).replace('T', ' ')} UT\nFür alle Nutzer aktiv.`
|
||||||
: `Fehler: ${ok}`;
|
: `Fehler: ${ok}`;
|
||||||
|
if (ok === true) {
|
||||||
|
const res = await fetch('jupitersystem.php?grs_api').catch(() => null);
|
||||||
|
if (res && res.ok) { const d = await res.json(); renderGrsDrift(d.history); }
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user