TCRB und Datenbank umgebaut für Imort Runs

This commit is contained in:
2026-04-05 09:43:19 +02:00
parent cbeced3a2a
commit 39685ac989
9 changed files with 2012 additions and 8 deletions
+1
View File
@@ -55,6 +55,7 @@ $menuGroups = [
['href' => 'moonphase.php', 'label' => 'Mondphase'],
['href' => 'index_py.php', 'label' => 'Sonne & Mond'],
['href' => 'planetensichtbarkeit.php', 'label' => 'Planetensichtbarkeit'],
['href' => 'tcrb_lightcurve.php', 'label' => 'T CrB Lichtkurve'],
['href' => 'solarsystem.php', 'label' => 'Sonnensystem'],
['href' => 'jupitersystem.php', 'label' => 'Jupitersystem'],
['href' => 'tagbogen.php', 'label' => 'Tagbogen'],
+554
View File
@@ -0,0 +1,554 @@
<?php
declare(strict_types=1);
ini_set('display_errors', '1');
error_reporting(E_ALL);
session_start();
$config = require __DIR__ . '/../config/database.php';
$dsn = sprintf(
'mysql:host=%s;dbname=%s;charset=%s',
$config['host'],
$config['dbname'],
$config['charset'] ?? 'utf8mb4'
);
$pdo = new PDO(
$dsn,
$config['user'],
$config['pass'],
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]
);
$loggedIn = isset($_SESSION['user_id']);
$pageTitle = 'T CrB Lichtkurve';
$headerIntroPre = 'Variabler Stern';
$headerIntroTitle = 'T CrB Lichtkurve';
$headerIntroSub = 'AAVSO-WebObs-Daten mit waehlbaren Filtern in einem gemeinsamen Plotly-Diagramm.';
$errors = [];
function h(?string $value): string
{
return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
}
$filterRows = $pdo->query("
SELECT `filter_code`, COUNT(*) AS `entry_count`
FROM `aavso_tcrb_observations`
WHERE `filter_code` IS NOT NULL
AND `filter_code` <> ''
GROUP BY `filter_code`
ORDER BY `filter_code` ASC
")->fetchAll();
$availableFilters = [];
foreach ($filterRows as $filterRow) {
$filterCode = trim((string) ($filterRow['filter_code'] ?? ''));
if ($filterCode === '') {
continue;
}
$availableFilters[] = [
'code' => $filterCode,
'count' => (int) ($filterRow['entry_count'] ?? 0),
];
}
$selectedFilters = array_values(array_unique(array_filter(array_map(
static fn ($value): string => trim((string) $value),
is_array($_GET['filters'] ?? null) ? $_GET['filters'] : []
), static fn (string $value): bool => $value !== '')));
if (empty($selectedFilters)) {
$selectedFilters = in_array('V', array_column($availableFilters, 'code'), true)
? ['V']
: array_column($availableFilters, 'code');
}
$selectedFilters = array_values(array_intersect($selectedFilters, array_column($availableFilters, 'code')));
if (empty($availableFilters)) {
$errors[] = 'In der Tabelle aavso_tcrb_observations sind noch keine T-CrB-Daten vorhanden.';
}
$observations = [];
$summary = [
'count' => 0,
'min_utc' => null,
'max_utc' => null,
'min_mag' => null,
'max_mag' => null,
];
if (empty($errors) && !empty($selectedFilters)) {
$placeholders = implode(',', array_fill(0, count($selectedFilters), '?'));
$stmt = $pdo->prepare("
SELECT
`observed_at_utc`,
`jd`,
`magnitude`,
`magnitude_error`,
`filter_code`,
`observer_code`,
`notes_raw`
FROM `aavso_tcrb_observations`
WHERE `filter_code` IN ({$placeholders})
ORDER BY `observed_at_utc` ASC, `jd` ASC
");
$stmt->execute($selectedFilters);
$rows = $stmt->fetchAll();
foreach ($rows as $row) {
$observedAtUtc = (string) ($row['observed_at_utc'] ?? '');
$magnitude = isset($row['magnitude']) ? (float) $row['magnitude'] : null;
if ($observedAtUtc === '' || $magnitude === null) {
continue;
}
$observations[] = [
'observed_at_utc' => $observedAtUtc,
'jd' => isset($row['jd']) ? (float) $row['jd'] : null,
'magnitude' => $magnitude,
'magnitude_error' => $row['magnitude_error'] !== null ? (float) $row['magnitude_error'] : null,
'filter_code' => (string) ($row['filter_code'] ?? ''),
'observer_code' => (string) ($row['observer_code'] ?? ''),
'notes_raw' => (string) ($row['notes_raw'] ?? ''),
];
$summary['count']++;
$summary['min_utc'] = $summary['min_utc'] === null || $observedAtUtc < $summary['min_utc'] ? $observedAtUtc : $summary['min_utc'];
$summary['max_utc'] = $summary['max_utc'] === null || $observedAtUtc > $summary['max_utc'] ? $observedAtUtc : $summary['max_utc'];
$summary['min_mag'] = $summary['min_mag'] === null || $magnitude < $summary['min_mag'] ? $magnitude : $summary['min_mag'];
$summary['max_mag'] = $summary['max_mag'] === null || $magnitude > $summary['max_mag'] ? $magnitude : $summary['max_mag'];
}
}
if (empty($errors) && empty($observations)) {
$errors[] = 'Fuer die ausgewaehlten Filter wurden keine Daten gefunden.';
}
$filterPalette = [
'U' => '#4c6ef5',
'B' => '#1c7ed6',
'V' => '#2b8a3e',
'R' => '#c92a2a',
'I' => '#862e9c',
'CV' => '#495057',
'TG' => '#0b7285',
'TB' => '#364fc7',
'TR' => '#d9480f',
];
$chartDatasets = [];
foreach ($selectedFilters as $selectedFilter) {
$chartDatasets[$selectedFilter] = [
'filter' => $selectedFilter,
'color' => $filterPalette[$selectedFilter] ?? sprintf('#%06X', crc32($selectedFilter) & 0xFFFFFF),
'points' => [],
];
}
foreach ($observations as $observation) {
$filterCode = $observation['filter_code'];
if (!isset($chartDatasets[$filterCode])) {
continue;
}
$chartDatasets[$filterCode]['points'][] = $observation;
}
$chartDatasets = array_values(array_filter($chartDatasets, static fn (array $dataset): bool => !empty($dataset['points'])));
$plotlyTraces = [];
foreach ($chartDatasets as $dataset) {
$xValues = [];
$yValues = [];
$errorValues = [];
$customData = [];
$hasErrors = false;
foreach ($dataset['points'] as $point) {
$xValues[] = (string) $point['observed_at_utc'];
$yValues[] = (float) $point['magnitude'];
$errorValue = $point['magnitude_error'] !== null ? (float) $point['magnitude_error'] : 0.0;
$errorValues[] = $errorValue;
if ($errorValue > 0) {
$hasErrors = true;
}
$customData[] = [
$point['jd'],
$point['observer_code'] !== '' ? $point['observer_code'] : '—',
$point['magnitude_error'] !== null ? number_format((float) $point['magnitude_error'], 3, '.', '') : '—',
];
}
$plotlyTraces[] = [
'type' => 'scatter',
'mode' => 'lines+markers',
'name' => $dataset['filter'],
'x' => $xValues,
'y' => $yValues,
'line' => [
'color' => $dataset['color'],
'width' => 2,
],
'marker' => [
'color' => $dataset['color'],
'size' => 7,
'line' => [
'color' => '#0f172a',
'width' => 1,
],
],
'error_y' => [
'type' => 'data',
'array' => $errorValues,
'visible' => $hasErrors,
'thickness' => 1.1,
'width' => 3,
'color' => $dataset['color'],
],
'customdata' => $customData,
'hovertemplate' => 'Filter %{fullData.name}<br>'
. 'Magnitude %{y:.3f}<br>'
. 'Fehler %{customdata[2]}<br>'
. 'UTC %{x}<br>'
. 'JD %{customdata[0]}<br>'
. 'Observer %{customdata[1]}<extra></extra>',
];
}
$meanSourcePoints = [];
foreach ($chartDatasets as $dataset) {
foreach ($dataset['points'] as $point) {
$meanSourcePoints[] = $point;
}
}
usort($meanSourcePoints, static function (array $a, array $b): int {
return strcmp((string) $a['observed_at_utc'], (string) $b['observed_at_utc']);
});
if (count($meanSourcePoints) >= 3) {
$windowSize = min(11, count($meanSourcePoints));
if ($windowSize % 2 === 0) {
$windowSize--;
}
$halfWindow = intdiv($windowSize, 2);
$meanX = [];
$meanY = [];
$meanCustomData = [];
foreach ($meanSourcePoints as $index => $point) {
$start = max(0, $index - $halfWindow);
$end = min(count($meanSourcePoints) - 1, $index + $halfWindow);
$sum = 0.0;
$count = 0;
for ($i = $start; $i <= $end; $i++) {
$sum += (float) $meanSourcePoints[$i]['magnitude'];
$count++;
}
if ($count === 0) {
continue;
}
$meanValue = $sum / $count;
$meanX[] = (string) $point['observed_at_utc'];
$meanY[] = $meanValue;
$meanCustomData[] = [$count];
}
if (!empty($meanX)) {
$plotlyTraces[] = [
'type' => 'scatter',
'mode' => 'lines',
'name' => 'Mittelkurve',
'x' => $meanX,
'y' => $meanY,
'line' => [
'color' => '#f4d35e',
'width' => 3,
'dash' => 'solid',
'shape' => 'spline',
'smoothing' => 0.7,
],
'customdata' => $meanCustomData,
'hovertemplate' => 'Mittelkurve<br>'
. 'Magnitude %{y:.3f}<br>'
. 'UTC %{x}<br>'
. 'Fenster %{customdata[0]} Punkte<extra></extra>',
];
}
}
$plotlyPayload = [
'traces' => $plotlyTraces,
'summary' => $summary,
];
?>
<?php require __DIR__ . '/header.php'; ?>
<script src="https://cdn.plot.ly/plotly-3.2.0.min.js" charset="utf-8"></script>
<?php if (!empty($errors)): ?>
<ul class="msg-error">
<?php foreach ($errors as $error): ?>
<li><?= h($error) ?></li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
<style>
.tcrb-layout {
display: grid;
grid-template-columns: minmax(18rem, 22rem) minmax(0, 1fr);
gap: 1.2rem;
align-items: start;
}
.tcrb-filter-list {
display: grid;
gap: 0.55rem;
}
.tcrb-filter-option {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.8rem;
padding: 0.55rem 0.7rem;
border: 1px solid rgba(255,255,255,0.12);
border-radius: 0.7rem;
background: rgba(255,255,255,0.03);
}
.tcrb-filter-option label {
display: flex;
align-items: center;
gap: 0.55rem;
margin: 0;
cursor: pointer;
}
.tcrb-filter-swatch {
width: 0.9rem;
height: 0.9rem;
border-radius: 999px;
border: 1px solid rgba(255,255,255,0.4);
}
.tcrb-filter-count {
color: var(--text-dim);
font-size: 0.9rem;
}
.tcrb-chart-card {
overflow: hidden;
}
.tcrb-chart-wrap {
position: relative;
min-height: 32rem;
border-radius: 1rem;
background:
radial-gradient(circle at top, rgba(200, 168, 75, 0.12), transparent 45%),
linear-gradient(180deg, rgba(17, 24, 39, 0.96), rgba(7, 10, 18, 0.98));
border: 1px solid rgba(200, 168, 75, 0.2);
overflow: hidden;
}
.tcrb-plot {
width: 100%;
height: 100%;
}
.tcrb-chart-legend {
display: flex;
flex-wrap: wrap;
gap: 0.7rem 1rem;
margin-top: 1rem;
}
.tcrb-chart-legend-item {
display: inline-flex;
align-items: center;
gap: 0.45rem;
color: #dfe7ff;
font-size: 0.92rem;
}
.tcrb-chart-legend-item span {
width: 0.85rem;
height: 0.85rem;
border-radius: 999px;
}
.tcrb-summary-grid {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 0.8rem;
margin-bottom: 1rem;
}
.tcrb-summary-box {
padding: 0.8rem 0.9rem;
border-radius: 0.9rem;
background: rgba(255,255,255,0.04);
border: 1px solid rgba(255,255,255,0.08);
}
.tcrb-summary-box strong {
display: block;
font-size: 1.05rem;
color: #f9fafb;
}
.tcrb-summary-box span {
display: block;
color: var(--text-dim);
font-size: 0.88rem;
margin-top: 0.2rem;
}
.tcrb-chart-empty {
padding: 2rem 1.2rem;
color: var(--text-dim);
}
@media (max-width: 960px) {
.tcrb-layout {
grid-template-columns: 1fr;
}
.tcrb-summary-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
</style>
<div class="tcrb-layout">
<div class="card">
<h2>Filter</h2>
<form method="get" action="" class="form-stack">
<div class="tcrb-filter-list">
<?php foreach ($availableFilters as $filter): ?>
<?php
$filterCode = $filter['code'];
$filterColor = $filterPalette[$filterCode] ?? sprintf('#%06X', crc32($filterCode) & 0xFFFFFF);
?>
<div class="tcrb-filter-option">
<label>
<input
type="checkbox"
name="filters[]"
value="<?= h($filterCode) ?>"
<?= in_array($filterCode, $selectedFilters, true) ? 'checked' : '' ?>
>
<span class="tcrb-filter-swatch" style="background: <?= h($filterColor) ?>"></span>
<span><?= h($filterCode) ?></span>
</label>
<span class="tcrb-filter-count"><?= (int) $filter['count'] ?></span>
</div>
<?php endforeach; ?>
</div>
<button type="submit" class="btn btn-primary">Diagramm aktualisieren</button>
</form>
</div>
<div class="card tcrb-chart-card">
<h2>Lichtkurve</h2>
<?php if (empty($errors) && !empty($observations)): ?>
<div class="tcrb-summary-grid">
<div class="tcrb-summary-box">
<strong><?= (int) $summary['count'] ?></strong>
<span>Messpunkte</span>
</div>
<div class="tcrb-summary-box">
<strong><?= h((string) $summary['min_utc']) ?></strong>
<span>Erster UTC-Zeitpunkt</span>
</div>
<div class="tcrb-summary-box">
<strong><?= h((string) $summary['max_utc']) ?></strong>
<span>Letzter UTC-Zeitpunkt</span>
</div>
<div class="tcrb-summary-box">
<strong><?= number_format((float) $summary['min_mag'], 3, '.', '') ?> bis <?= number_format((float) $summary['max_mag'], 3, '.', '') ?></strong>
<span>Magnitudenbereich</span>
</div>
</div>
<div class="tcrb-chart-wrap">
<div class="tcrb-plot" id="tcrbPlot" aria-label="T-CrB-Lichtkurve"></div>
</div>
<div class="tcrb-chart-legend">
<?php foreach ($chartDatasets as $dataset): ?>
<div class="tcrb-chart-legend-item">
<span style="background: <?= h($dataset['color']) ?>"></span>
<?= h($dataset['filter']) ?>
</div>
<?php endforeach; ?>
</div>
<?php else: ?>
<div class="tcrb-chart-empty">Noch keine darstellbaren Daten vorhanden.</div>
<?php endif; ?>
</div>
</div>
<?php if (empty($errors) && !empty($observations)): ?>
<script>
(function () {
const payload = <?= json_encode($plotlyPayload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
const plotEl = document.getElementById('tcrbPlot');
if (!plotEl || typeof Plotly === 'undefined' || !payload || !Array.isArray(payload.traces) || payload.traces.length === 0) {
return;
}
const layout = {
margin: { t: 20, r: 24, b: 56, l: 72 },
paper_bgcolor: 'rgba(0,0,0,0)',
plot_bgcolor: 'rgba(0,0,0,0)',
hovermode: 'closest',
showlegend: false,
xaxis: {
title: { text: 'Beobachtungszeitpunkt (UTC)' },
type: 'date',
gridcolor: 'rgba(255,255,255,0.08)',
linecolor: '#f8fafc',
tickfont: { color: '#cbd5e1' },
zeroline: false
},
yaxis: {
title: { text: 'Magnitude (kleiner = heller)' },
autorange: 'reversed',
gridcolor: 'rgba(255,255,255,0.12)',
linecolor: '#f8fafc',
tickfont: { color: '#cbd5e1' },
zeroline: false
},
font: {
color: '#f8fafc'
}
};
const config = {
responsive: true,
displaylogo: false,
modeBarButtonsToRemove: ['select2d', 'lasso2d']
};
Plotly.newPlot(plotEl, payload.traces, layout, config);
})();
</script>
<?php endif; ?>
<?php require __DIR__ . '/footer.php'; ?>