Nutze Skyfield fuer praezisere Daemmerungsberechnung
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
declare(strict_types=1);
|
||||
|
||||
session_start();
|
||||
require_once __DIR__ . '/python_api.php';
|
||||
|
||||
$pageTitle = 'Dämmerungszeiten im Zeitraum - AstroTools';
|
||||
$bodyClass = 'daemmerungszeiten-page';
|
||||
@@ -55,6 +56,29 @@ if (isset($_SESSION['user_id'])) {
|
||||
}
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && (string) ($_POST['ajax'] ?? '') === '1') {
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
$pythonResult = runPythonApiWithJsonPayloadFile('twilight_chart', [
|
||||
'latitude' => (float) ($_POST['latitude'] ?? 0),
|
||||
'longitude' => (float) ($_POST['longitude'] ?? 0),
|
||||
'timeZone' => trim((string) ($_POST['timeZone'] ?? '')),
|
||||
'startDate' => trim((string) ($_POST['startDate'] ?? '')),
|
||||
'endDate' => trim((string) ($_POST['endDate'] ?? '')),
|
||||
]);
|
||||
|
||||
if (!$pythonResult['ok']) {
|
||||
echo json_encode([
|
||||
'ok' => false,
|
||||
'error' => (string) ($pythonResult['error'] ?? 'Python-Fehler'),
|
||||
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
exit;
|
||||
}
|
||||
|
||||
echo json_encode($pythonResult['data'], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
exit;
|
||||
}
|
||||
|
||||
require __DIR__ . '/header.php';
|
||||
|
||||
$initialLocation = [
|
||||
@@ -81,8 +105,6 @@ $defaultStartDate = date('Y-01-01');
|
||||
$defaultEndDate = date('Y-12-31');
|
||||
?>
|
||||
|
||||
<script src="js/astronomy.browser.min.js"></script>
|
||||
|
||||
<style>
|
||||
body.daemmerungszeiten-page main > .container {
|
||||
max-width: min(1700px, calc(100% - 32px));
|
||||
@@ -347,6 +369,9 @@ body.daemmerungszeiten-page main > .container {
|
||||
<p class="twilight-help">
|
||||
Die Grafik zeigt jeden Tag des gewählten Zeitraums als senkrechte Spur. Von oben nach unten läuft die lokale Uhrzeit des Standorts, farblich getrennt nach Nacht, astronomischer, nautischer und bürgerlicher Dämmerung sowie Tag.
|
||||
</p>
|
||||
<p class="twilight-help">
|
||||
Die Dämmerungsgrenzen werden serverseitig mit <strong>Skyfield</strong> und lokalen JPL-Ephemeriden berechnet, damit besonders die Übergänge an der astronomischen Dämmerung möglichst präzise bleiben.
|
||||
</p>
|
||||
<div class="twilight-form-grid">
|
||||
<div class="twilight-field">
|
||||
<label for="presetLocation">Gespeicherte Standorte</label>
|
||||
@@ -1090,6 +1115,35 @@ function nextFrame() {
|
||||
return new Promise((resolve) => requestAnimationFrame(resolve));
|
||||
}
|
||||
|
||||
async function requestTwilightChartData(state) {
|
||||
const formData = new FormData();
|
||||
formData.set('ajax', '1');
|
||||
formData.set('latitude', String(state.latitude));
|
||||
formData.set('longitude', String(state.longitude));
|
||||
formData.set('timeZone', state.timeZone);
|
||||
formData.set('startDate', state.startDateIso);
|
||||
formData.set('endDate', state.endDateIso);
|
||||
|
||||
const response = await fetch(window.location.href, {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Serverfehler ${response.status} bei der Dämmerungsberechnung.`);
|
||||
}
|
||||
|
||||
const payload = await response.json();
|
||||
if (!payload || payload.ok !== true || !payload.result) {
|
||||
throw new Error(payload && payload.error ? payload.error : 'Die Dämmerungsdaten konnten nicht geladen werden.');
|
||||
}
|
||||
|
||||
return payload.result;
|
||||
}
|
||||
|
||||
async function buildPixelCurves(chart, columns) {
|
||||
const clampedColumns = Math.max(32, Math.floor(columns));
|
||||
const pixelCurves = [];
|
||||
@@ -1182,69 +1236,41 @@ async function buildDisplayRaster(chart, columns, stepMinutes = 1) {
|
||||
}
|
||||
|
||||
async function buildChartData(state, token) {
|
||||
const totalDays = state.dayDates.length;
|
||||
renderStatus.textContent = 'Lade Dämmerungsdaten …';
|
||||
const serverChart = await requestTwilightChartData(state);
|
||||
if (token !== currentRenderToken) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const totalDays = Number(serverChart.totalDays) || state.dayDates.length;
|
||||
const rows = Math.floor((24 * 60) / state.stepMinutes);
|
||||
const observer = new Astronomy.Observer(state.latitude, state.longitude, 0);
|
||||
const dayCurves = Array.isArray(serverChart.dayCurves) ? serverChart.dayCurves : [];
|
||||
const phaseGrid = new Uint8Array(totalDays * rows);
|
||||
const dayCurves = [];
|
||||
const imageCanvas = document.createElement('canvas');
|
||||
imageCanvas.width = totalDays;
|
||||
imageCanvas.height = rows;
|
||||
const imageContext = imageCanvas.getContext('2d');
|
||||
const image = imageContext.createImageData(totalDays, rows);
|
||||
const xAxisTicks = buildXAxisTicks(state.dayDates, state.timeZone);
|
||||
let longestDayMinutes = -1;
|
||||
let longestDayIndex = 0;
|
||||
let longestNightMinutes = -1;
|
||||
let longestNightIndex = 0;
|
||||
|
||||
for (let dayIndex = 0; dayIndex < totalDays; dayIndex += 1) {
|
||||
if (token !== currentRenderToken) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const localDate = localDateFromDayIndex(state.dayDates, dayIndex);
|
||||
const localParts = getTimeZoneParts(localDate, state.timeZone);
|
||||
const dayCurve = buildThresholdIntervals(observer, localParts, state.timeZone);
|
||||
dayCurves.push(dayCurve);
|
||||
|
||||
let dayMinutes = 0;
|
||||
let astroNightMinutes = 0;
|
||||
|
||||
const dayCurve = dayCurves[dayIndex];
|
||||
for (let rowIndex = 0; rowIndex < rows; rowIndex += 1) {
|
||||
const minutesOfDay = rowIndex * state.stepMinutes;
|
||||
const phaseIndex = phaseIndexFromCurve(dayCurve, minutesOfDay);
|
||||
const offset = rowIndex * totalDays + dayIndex;
|
||||
const minutesOfDay = rowIndex * state.stepMinutes;
|
||||
const phaseIndex = dayCurve ? phaseIndexFromCurve(dayCurve, minutesOfDay) : 0;
|
||||
const imageOffset = offset * 4;
|
||||
const color = PHASES[phaseIndex].color;
|
||||
const color = PHASES[phaseIndex] ? PHASES[phaseIndex].color : PHASES[0].color;
|
||||
|
||||
phaseGrid[offset] = phaseIndex;
|
||||
image.data[imageOffset] = color[0];
|
||||
image.data[imageOffset + 1] = color[1];
|
||||
image.data[imageOffset + 2] = color[2];
|
||||
image.data[imageOffset + 3] = color[3];
|
||||
|
||||
if (phaseIndex === 4) {
|
||||
dayMinutes += state.stepMinutes;
|
||||
}
|
||||
|
||||
if (phaseIndex === 0) {
|
||||
astroNightMinutes += state.stepMinutes;
|
||||
}
|
||||
}
|
||||
|
||||
if (dayMinutes > longestDayMinutes) {
|
||||
longestDayMinutes = dayMinutes;
|
||||
longestDayIndex = dayIndex;
|
||||
}
|
||||
|
||||
if (astroNightMinutes > longestNightMinutes) {
|
||||
longestNightMinutes = astroNightMinutes;
|
||||
longestNightIndex = dayIndex;
|
||||
}
|
||||
|
||||
if (dayIndex % 8 === 0) {
|
||||
renderStatus.textContent = `Berechne ${dayIndex + 1} / ${totalDays} Tage …`;
|
||||
if (dayIndex % 10 === 0) {
|
||||
renderStatus.textContent = `Baue Raster ${dayIndex + 1} / ${totalDays} …`;
|
||||
await nextFrame();
|
||||
}
|
||||
}
|
||||
@@ -1255,7 +1281,6 @@ async function buildChartData(state, token) {
|
||||
state,
|
||||
totalDays,
|
||||
rows,
|
||||
observer,
|
||||
phaseGrid,
|
||||
previewPhaseGrid: phaseGrid,
|
||||
previewRows: rows,
|
||||
@@ -1263,10 +1288,10 @@ async function buildChartData(state, token) {
|
||||
dayCurves,
|
||||
imageCanvas,
|
||||
xAxisTicks,
|
||||
longestDayIndex,
|
||||
longestDayMinutes,
|
||||
longestNightIndex,
|
||||
longestNightMinutes
|
||||
longestDayIndex: Number(serverChart.longestDayIndex) || 0,
|
||||
longestDayMinutes: Number(serverChart.longestDayMinutes) || 0,
|
||||
longestNightIndex: Number(serverChart.longestNightIndex) || 0,
|
||||
longestNightMinutes: Number(serverChart.longestNightMinutes) || 0
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1730,15 +1755,10 @@ function showTooltip(event) {
|
||||
const hour = Math.floor(minutesOfDay / 60);
|
||||
const minute = minutesOfDay % 60;
|
||||
const localDate = localDateFromDayIndex(currentChart.state.dayDates, dayIndex);
|
||||
const localParts = getTimeZoneParts(localDate, currentChart.state.timeZone);
|
||||
const utcDate = zonedDateTimeToUtc(localParts.year, localParts.month, localParts.day, hour, minute, currentChart.state.timeZone);
|
||||
const altitude = sunAltitude(currentChart.observer, utcDate);
|
||||
|
||||
tooltip.innerHTML = `
|
||||
<strong>${formatDateGerman(localDate, currentChart.state.timeZone)}</strong>
|
||||
<div class="twilight-tooltip-line">${String(hour).padStart(2, '0')}:${String(minute).padStart(2, '0')} Uhr lokal</div>
|
||||
<div class="twilight-tooltip-line">${PHASES[phaseIndex].label}</div>
|
||||
<div class="twilight-tooltip-line">Sonnenhöhe: ${altitude.toFixed(1)}°</div>
|
||||
`;
|
||||
|
||||
tooltip.style.left = `${Math.min(rect.width - 240, Math.max(10, x + 10))}px`;
|
||||
|
||||
Reference in New Issue
Block a user