Verbessere Baryzentrum-Darstellung und Tagesauflösung

This commit is contained in:
2026-07-13 20:33:15 +02:00
parent 12ab61355e
commit e2689abe75
2 changed files with 122 additions and 48 deletions
+102 -41
View File
@@ -4,7 +4,7 @@ session_start();
require_once __DIR__ . '/python_api.php';
$result = runPythonApi('barycenter', ['20', '12', '24']);
$result = runPythonApi('barycenter', ['20', '365', '24']);
require __DIR__ . '/header.php';
@@ -135,6 +135,25 @@ require __DIR__ . '/header.php';
color: var(--gold-pale);
min-width: 60px;
}
.bc-toggle-row {
display: flex;
flex-wrap: wrap;
gap: 0.65rem 1rem;
margin-top: 0.65rem;
}
.bc-toggle {
display: inline-flex;
align-items: center;
gap: 0.45rem;
font-size: 0.82rem;
color: var(--text-dim);
cursor: pointer;
user-select: none;
}
.bc-toggle input {
accent-color: var(--gold);
cursor: pointer;
}
input[type=range].bc-slider {
flex: 1;
min-width: 120px;
@@ -169,29 +188,30 @@ input[type=range].bc-slider {
<?php else:
$cur = $result['data']['current'];
$distSR = $cur['dist_solar_radii'];
$projDistSR = $cur['proj_dist_solar_radii'];
$inside = $cur['inside_sun'];
$contributions = $result['data']['contributions'];
?>
<div class="bc-stats-row">
<div class="bc-stat">
<span class="bc-stat-label">Abstand vom Sonnenmittelpunkt</span>
<span class="bc-stat-label">Räumlicher Abstand vom Sonnenmittelpunkt</span>
<span class="bc-stat-value"><?= number_format($distSR, 3, ',', '') ?> R☉</span>
<span class="bc-stat-sub"><?= number_format($cur['dist_au'], 5, ',', '') ?> AU</span>
<span class="bc-stat-sub">XY-Projektion: <?= number_format($projDistSR, 3, ',', '') ?> R☉ · <?= number_format($cur['dist_au'], 5, ',', '') ?> AU</span>
</div>
<div class="bc-stat">
<span class="bc-stat-label">Position</span>
<span class="bc-stat-label">Position in der Grafik</span>
<span class="bc-stat-value">
<span class="bc-inside-badge <?= $inside ? 'inside' : 'outside' ?>">
<?= $inside ? 'Innerhalb der Sonne' : 'Außerhalb der Sonne' ?>
</span>
</span>
<span class="bc-stat-sub">Stand: <?= htmlspecialchars($cur['date_utc'], ENT_QUOTES, 'UTF-8') ?></span>
<span class="bc-stat-sub">Bewertet als XY-Projektion · Stand: <?= htmlspecialchars($cur['date_utc'], ENT_QUOTES, 'UTF-8') ?></span>
</div>
<div class="bc-stat">
<span class="bc-stat-label">Maximum (2006–2050)</span>
<span class="bc-stat-value"><?= number_format($result['data']['max_dist_solar_radii'], 3, ',', '') ?> R☉</span>
<span class="bc-stat-sub">Maximaler Abstand im Zeitraum</span>
<span class="bc-stat-label">Maximum der Grafik (2006–2050)</span>
<span class="bc-stat-value"><?= number_format($result['data']['max_proj_dist_solar_radii'], 3, ',', '') ?> R☉</span>
<span class="bc-stat-sub">Größter projizierter Abstand im Zeitraum</span>
</div>
</div>
@@ -205,8 +225,7 @@ input[type=range].bc-slider {
<div class="bc-legend">
<span class="bc-legend-item"><span class="bc-legend-dot" style="background:#f5c842"></span> Sonne</span>
<span class="bc-legend-item"><span class="bc-legend-dot" style="background:rgba(255,255,255,0.25);border:1px dashed #666"></span> Sonnenoberfläche</span>
<span class="bc-legend-item"><span class="bc-legend-dot" style="background:#4fc3d8"></span> Baryzentrum (Vergangenheit)</span>
<span class="bc-legend-item"><span class="bc-legend-dot" style="background:#c9a84c;opacity:0.7"></span> Baryzentrum (Prognose)</span>
<span class="bc-legend-item"><span class="bc-legend-dot" style="background:#4fc3d8"></span> Baryzentrum-Bahn</span>
<span class="bc-legend-item"><span class="bc-legend-dot" style="background:#ff6060"></span> Baryzentrum (heute)</span>
</div>
<div class="bc-anim-controls">
@@ -214,6 +233,16 @@ input[type=range].bc-slider {
<input type="range" class="bc-slider" id="bc-slider" min="0" max="240" value="240" step="1">
<span class="bc-year-label" id="bc-year-label">heute</span>
</div>
<div class="bc-toggle-row">
<label class="bc-toggle">
<input type="checkbox" id="bc-toggle-path" checked>
<span>Baryzentrum-Linie</span>
</label>
<label class="bc-toggle">
<input type="checkbox" id="bc-toggle-radii" checked>
<span>1R- und 2R-Radius</span>
</label>
</div>
</div>
</div>
<div>
@@ -233,6 +262,10 @@ input[type=range].bc-slider {
Die Bahn folgt keinem einfachen Kreis –
die Überlagerung der Planetenumläufe erzeugt eine komplexe Spirale.
</p>
<p style="font-size:0.9rem;color:var(--text-dim);line-height:1.6;margin-top:0.75rem">
Die Grafik ist eine XY-Projektion. Deshalb wird die Lage relativ zur Sonnenscheibe
hier ebenfalls in der Projektion bewertet, nicht mit dem räumlichen 3D-Abstand.
</p>
</div>
<div class="card" style="margin-top:1.5rem">
@@ -279,7 +312,7 @@ input[type=range].bc-slider {
const CUR = <?= json_encode($cur, JSON_UNESCAPED_UNICODE) ?>;
const NOW_INDEX = <?= (int)$result['data']['now_index'] ?>;
const SUN_RADIUS_AU = <?= json_encode($result['data']['sun_radius_au']) ?>;
const MAX_DIST_SR = <?= json_encode($result['data']['max_dist_solar_radii']) ?>;
const MAX_DIST_SR = <?= json_encode($result['data']['max_proj_dist_solar_radii']) ?>;
const canvas = document.getElementById('bc-canvas');
const ctx = canvas.getContext('2d');
@@ -292,13 +325,19 @@ input[type=range].bc-slider {
const slider = document.getElementById('bc-slider');
const playBtn = document.getElementById('bc-play');
const yearLabel = document.getElementById('bc-year-label');
const pathToggle = document.getElementById('bc-toggle-path');
const radiiToggle = document.getElementById('bc-toggle-radii');
slider.max = SERIES.length - 1;
slider.value = NOW_INDEX;
function formatDate(yearFrac) {
const y = Math.floor(yearFrac);
const m = Math.round((yearFrac - y) * 12) + 1;
function formatDate(dateValue) {
if (typeof dateValue === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(dateValue)) {
return dateValue;
}
const y = Math.floor(dateValue);
const m = Math.round((dateValue - y) * 12) + 1;
return y + '-' + String(m).padStart(2, '0');
}
@@ -322,20 +361,22 @@ input[type=range].bc-slider {
ctx.clearRect(0, 0, W, H);
// Background grid rings
ctx.strokeStyle = 'rgba(255,255,255,0.05)';
ctx.lineWidth = 1;
for (let r = 1; r <= Math.ceil(MAX_DIST_SR * 1.2); r++) {
ctx.beginPath();
ctx.arc(cx, cy, r * scale, 0, Math.PI * 2);
ctx.stroke();
}
// Ring labels
ctx.font = '10px sans-serif';
ctx.fillStyle = 'rgba(255,255,255,0.25)';
ctx.textAlign = 'left';
for (let r = 1; r <= Math.ceil(MAX_DIST_SR * 1.2); r++) {
ctx.fillText(r + ' R☉', cx + r * scale + 3, cy - 3);
if (radiiToggle.checked) {
const guideRadii = [1, 2];
ctx.strokeStyle = 'rgba(255,255,255,0.07)';
ctx.lineWidth = 1;
ctx.font = '10px sans-serif';
ctx.fillStyle = 'rgba(255,255,255,0.25)';
ctx.textAlign = 'left';
for (const r of guideRadii) {
if (r > MAX_DIST_SR * 1.25) {
continue;
}
ctx.beginPath();
ctx.arc(cx, cy, r * scale, 0, Math.PI * 2);
ctx.stroke();
ctx.fillText(r + ' R☉', cx + r * scale + 3, cy - 3);
}
}
// Sun glow
@@ -354,21 +395,22 @@ input[type=range].bc-slider {
ctx.fillStyle = '#f5c842';
ctx.fill();
// Sun surface dashed circle
ctx.beginPath();
ctx.arc(cx, cy, sunRadiusPx, 0, Math.PI * 2);
ctx.setLineDash([4, 4]);
ctx.strokeStyle = 'rgba(255,255,255,0.2)';
ctx.lineWidth = 1;
ctx.stroke();
ctx.setLineDash([]);
if (radiiToggle.checked) {
ctx.beginPath();
ctx.arc(cx, cy, sunRadiusPx, 0, Math.PI * 2);
ctx.setLineDash([4, 4]);
ctx.strokeStyle = 'rgba(255,255,255,0.2)';
ctx.lineWidth = 1;
ctx.stroke();
ctx.setLineDash([]);
}
// Barycenter path up to visibleUpTo
const end = Math.min(visibleUpTo, SERIES.length - 1);
// Vergangener Pfad (bis NOW_INDEX) — cyan
const pastEnd = Math.min(end, NOW_INDEX);
if (pastEnd >= 1) {
if (pathToggle.checked && pastEnd >= 1) {
ctx.beginPath();
const p0 = SERIES[0];
ctx.moveTo(cx + p0.x / SUN_RADIUS_AU * scale, cy - p0.y / SUN_RADIUS_AU * scale);
@@ -383,7 +425,7 @@ input[type=range].bc-slider {
}
// Zukünftiger Pfad (ab NOW_INDEX) — gold gedimmt, gestrichelt
if (end > NOW_INDEX) {
if (pathToggle.checked && end > NOW_INDEX) {
ctx.beginPath();
const pn = SERIES[NOW_INDEX];
ctx.moveTo(cx + pn.x / SUN_RADIUS_AU * scale, cy - pn.y / SUN_RADIUS_AU * scale);
@@ -391,9 +433,9 @@ input[type=range].bc-slider {
const p = SERIES[i];
ctx.lineTo(cx + p.x / SUN_RADIUS_AU * scale, cy - p.y / SUN_RADIUS_AU * scale);
}
ctx.strokeStyle = 'rgba(201, 168, 76, 0.45)';
ctx.strokeStyle = 'rgba(79, 195, 216, 0.55)';
ctx.lineWidth = 1.5;
ctx.setLineDash([5, 4]);
ctx.setLineDash([]);
ctx.stroke();
ctx.setLineDash([]);
}
@@ -421,7 +463,7 @@ input[type=range].bc-slider {
ctx.font = '11px sans-serif';
ctx.fillStyle = 'rgba(255,255,255,0.65)';
ctx.textAlign = 'left';
ctx.fillText(pCur.d.toFixed(3) + ' R☉', px + 8, py - 6);
ctx.fillText(pCur.dp.toFixed(3) + ' R☉', px + 8, py - 6);
}
function updateLabel() {
@@ -433,12 +475,31 @@ input[type=range].bc-slider {
}
}
function setVisibleIndex(nextIndex) {
const clamped = Math.max(0, Math.min(SERIES.length - 1, nextIndex));
visibleUpTo = clamped;
slider.value = clamped;
updateLabel();
draw();
}
function handleWheelStep(event) {
event.preventDefault();
if (animPlaying) stopAnim();
const direction = event.deltaY > 0 ? 1 : -1;
setVisibleIndex(parseInt(slider.value, 10) + direction);
}
slider.addEventListener('input', () => {
visibleUpTo = parseInt(slider.value, 10);
updateLabel();
draw();
if (animPlaying) stopAnim();
});
slider.addEventListener('wheel', handleWheelStep, { passive: false });
canvas.addEventListener('wheel', handleWheelStep, { passive: false });
pathToggle.addEventListener('change', draw);
radiiToggle.addEventListener('change', draw);
function stopAnim() {
animPlaying = false;
+20 -7
View File
@@ -5780,7 +5780,7 @@ def action_barycenter(args: list[str]) -> dict:
steps_per_year = int(args[1]) if len(args) > 1 and args[1].isdigit() else 12
years_forward = int(args[2]) if len(args) > 2 and args[2].isdigit() else 0
years_back = max(1, min(years_back, 100))
steps_per_year = max(4, min(steps_per_year, 52))
steps_per_year = max(4, min(steps_per_year, 366))
years_forward = max(0, min(years_forward, 100))
now = datetime.now(timezone.utc)
@@ -5793,6 +5793,8 @@ def action_barycenter(args: list[str]) -> dict:
cur_y = -sun_state.y
cur_dist_au = math.hypot(cur_x, cur_y, -sun_state.z)
cur_dist_sr = cur_dist_au / _SUN_RADIUS_AU
cur_proj_dist_au = math.hypot(cur_x, cur_y)
cur_proj_dist_sr = cur_proj_dist_au / _SUN_RADIUS_AU
# -- Zeitreihe --
total_steps = (years_back + years_forward) * steps_per_year
@@ -5800,24 +5802,32 @@ def action_barycenter(args: list[str]) -> dict:
start_jd = now_time.tt - years_back * 365.25
series = []
max_dist_au = 0.0
max_proj_dist_au = 0.0
for i in range(total_steps + 1):
t = astronomy.Time(start_jd + i * days_per_step)
s = astronomy.BaryState(astronomy.Body.Sun, t)
tx, ty = -s.x, -s.y
d = math.hypot(tx, ty, -s.z)
dp = math.hypot(tx, ty)
if d > max_dist_au:
max_dist_au = d
if dp > max_proj_dist_au:
max_proj_dist_au = dp
dt_utc = datetime(2000, 1, 1, 12, tzinfo=timezone.utc) + timedelta(days=t.tt - 0.0)
# Epoche J2000.0 = 2000-01-01 12:00 TT ≈ UTC (Differenz <1 min hier vernachlässigt)
series.append({"x": round(tx, 6), "y": round(ty, 6), "d": round(d / _SUN_RADIUS_AU, 3)})
series.append({
"x": round(tx, 6),
"y": round(ty, 6),
"d": round(d / _SUN_RADIUS_AU, 3),
"dp": round(dp / _SUN_RADIUS_AU, 3),
})
# Zeitstempel für Animationslabel (nur Jahr.Monat reicht)
# Zeitstempel für Animationslabel als echte UTC-Kalenderdaten
series_dates = []
for i in range(total_steps + 1):
t_days = start_jd + i * days_per_step
# Grobe Umrechnung J2000-TT → Jahr
year_frac = 2000.0 + (t_days / 365.25)
series_dates.append(round(year_frac, 3))
dt_utc = datetime(2000, 1, 1, 12, tzinfo=timezone.utc) + timedelta(days=t_days)
series_dates.append(dt_utc.strftime("%Y-%m-%d"))
# -- Planetenmassen (GM relativ zur Sonne = 1) für gewichteten Beitrag --
# Quellen: IAU 2012 / astronomy-engine Konstanten
@@ -5872,13 +5882,16 @@ def action_barycenter(args: list[str]) -> dict:
"y": round(cur_y, 6),
"dist_au": round(cur_dist_au, 6),
"dist_solar_radii": round(cur_dist_sr, 4),
"inside_sun": cur_dist_sr <= 1.0,
"proj_dist_au": round(cur_proj_dist_au, 6),
"proj_dist_solar_radii": round(cur_proj_dist_sr, 4),
"inside_sun": cur_proj_dist_sr <= 1.0,
"date_utc": now.strftime("%Y-%m-%dT%H:%M:%SZ"),
},
"series": series,
"series_dates": series_dates,
"now_index": now_index,
"max_dist_solar_radii": round(max_dist_au / _SUN_RADIUS_AU, 3),
"max_proj_dist_solar_radii": round(max_proj_dist_au / _SUN_RADIUS_AU, 3),
"sun_radius_au": _SUN_RADIUS_AU,
"contributions": contributions,
"years_back": years_back,