diff --git a/config/filter_colors.json b/config/filter_colors.json
new file mode 100644
index 0000000..32bc51b
--- /dev/null
+++ b/config/filter_colors.json
@@ -0,0 +1,67 @@
+{
+ "U": {
+ "wavelength_nm": 366.3,
+ "color": "#1B2A6B",
+ "description": "Johnson U, Ultraviolett, ca. 366,3 nm"
+ },
+ "B": {
+ "wavelength_nm": 436.1,
+ "color": "#1D00FF",
+ "description": "Johnson B, Blau, ca. 436,1 nm"
+ },
+ "SG": {
+ "wavelength_nm": 477.0,
+ "color": "#00C8FF",
+ "description": "Sloan g, ca. 477,0 nm"
+ },
+ "TB": {
+ "wavelength_nm": 440.0,
+ "color": "#0000FF",
+ "description": "DSLR/OSC Blaukanal, ca. 440,0 nm"
+ },
+ "V": {
+ "wavelength_nm": 544.8,
+ "color": "#92FF00",
+ "description": "Johnson V, visuell/gruen, ca. 544,8 nm"
+ },
+ "TG": {
+ "wavelength_nm": 544.8,
+ "color": "#92FF00",
+ "description": "DSLR/OSC Gruenkanal, ca. 544,8 nm"
+ },
+ "CV": {
+ "wavelength_nm": 544.8,
+ "color": "#92FF00",
+ "description": "Clear auf V referenziert, ca. 544,8 nm"
+ },
+ "SR": {
+ "wavelength_nm": 623.1,
+ "color": "#FF6B00",
+ "description": "Sloan r, ca. 623,1 nm"
+ },
+ "R": {
+ "wavelength_nm": 640.7,
+ "color": "#FF1D00",
+ "description": "Cousins R, Rot, ca. 640,7 nm"
+ },
+ "TR": {
+ "wavelength_nm": 640.7,
+ "color": "#FF1D00",
+ "description": "DSLR/OSC Rotkanal, ca. 640,7 nm"
+ },
+ "SI": {
+ "wavelength_nm": 762.5,
+ "color": "#870000",
+ "description": "Sloan i, ca. 762,5 nm"
+ },
+ "I": {
+ "wavelength_nm": 798.0,
+ "color": "#7A0018",
+ "description": "Cousins I, nahes Infrarot, ca. 798,0 nm"
+ },
+ "SZ": {
+ "wavelength_nm": 909.7,
+ "color": "#7A0018",
+ "description": "Sloan z, ca. 909,7 nm"
+ }
+}
diff --git a/public/py/__pycache__/filter_colors.cpython-312.pyc b/public/py/__pycache__/filter_colors.cpython-312.pyc
new file mode 100644
index 0000000..0ae38b0
Binary files /dev/null and b/public/py/__pycache__/filter_colors.cpython-312.pyc differ
diff --git a/public/py/filter_colors.py b/public/py/filter_colors.py
new file mode 100644
index 0000000..64b5645
--- /dev/null
+++ b/public/py/filter_colors.py
@@ -0,0 +1,117 @@
+from __future__ import annotations
+
+import argparse
+import json
+from pathlib import Path
+
+
+FILTER_WAVELENGTHS_NM: dict[str, float] = {
+ "U": 366.3,
+ "B": 436.1,
+ "SG": 477.0,
+ "TB": 440.0,
+ "V": 544.8,
+ "TG": 544.8,
+ "CV": 544.8,
+ "SR": 623.1,
+ "R": 640.7,
+ "TR": 640.7,
+ "SI": 762.5,
+ "I": 798.0,
+ "SZ": 909.7,
+}
+
+
+FILTER_DESCRIPTIONS: dict[str, str] = {
+ "U": "Johnson U, Ultraviolett, ca. 366,3 nm",
+ "B": "Johnson B, Blau, ca. 436,1 nm",
+ "SG": "Sloan g, ca. 477,0 nm",
+ "TB": "DSLR/OSC Blaukanal, ca. 440,0 nm",
+ "V": "Johnson V, visuell/gruen, ca. 544,8 nm",
+ "TG": "DSLR/OSC Gruenkanal, ca. 544,8 nm",
+ "CV": "Clear auf V referenziert, ca. 544,8 nm",
+ "SR": "Sloan r, ca. 623,1 nm",
+ "R": "Cousins R, Rot, ca. 640,7 nm",
+ "TR": "DSLR/OSC Rotkanal, ca. 640,7 nm",
+ "SI": "Sloan i, ca. 762,5 nm",
+ "I": "Cousins I, nahes Infrarot, ca. 798,0 nm",
+ "SZ": "Sloan z, ca. 909,7 nm",
+}
+
+
+def wavelength_to_hex_color(wavelength_nm: float) -> str:
+ if wavelength_nm < 380:
+ return "#1B2A6B"
+
+ if wavelength_nm > 780:
+ return "#7A0018"
+
+ red = 0.0
+ green = 0.0
+ blue = 0.0
+
+ if wavelength_nm < 440:
+ red = -((wavelength_nm - 440) / (440 - 380))
+ blue = 1.0
+ elif wavelength_nm < 490:
+ green = (wavelength_nm - 440) / (490 - 440)
+ blue = 1.0
+ elif wavelength_nm < 510:
+ green = 1.0
+ blue = -((wavelength_nm - 510) / (510 - 490))
+ elif wavelength_nm < 580:
+ red = (wavelength_nm - 510) / (580 - 510)
+ green = 1.0
+ elif wavelength_nm < 645:
+ red = 1.0
+ green = -((wavelength_nm - 645) / (645 - 580))
+ else:
+ red = 1.0
+
+ factor = 1.0
+ if wavelength_nm < 420:
+ factor = 0.3 + 0.7 * ((wavelength_nm - 380) / (420 - 380))
+ elif wavelength_nm > 700:
+ factor = 0.3 + 0.7 * ((780 - wavelength_nm) / (780 - 700))
+
+ gamma = 0.8
+
+ def convert(channel: float) -> int:
+ if channel <= 0.0:
+ return 0
+ return round(255 * pow(channel * factor, gamma))
+
+ return "#{:02X}{:02X}{:02X}".format(convert(red), convert(green), convert(blue))
+
+
+def build_filter_metadata() -> dict[str, dict[str, object]]:
+ metadata: dict[str, dict[str, object]] = {}
+ for filter_code, wavelength_nm in FILTER_WAVELENGTHS_NM.items():
+ metadata[filter_code] = {
+ "wavelength_nm": wavelength_nm,
+ "color": wavelength_to_hex_color(wavelength_nm),
+ "description": FILTER_DESCRIPTIONS.get(filter_code, filter_code),
+ }
+ return metadata
+
+
+def main() -> int:
+ parser = argparse.ArgumentParser(description="Erzeugt Filter-Metadaten mit Wellenlaengen und RGB-Farben.")
+ parser.add_argument("--output", help="Pfad zur JSON-Ausgabedatei.")
+ args = parser.parse_args()
+
+ payload = build_filter_metadata()
+ json_text = json.dumps(payload, ensure_ascii=False, indent=2) + "\n"
+
+ if args.output:
+ output_path = Path(args.output)
+ output_path.parent.mkdir(parents=True, exist_ok=True)
+ output_path.write_text(json_text, encoding="utf-8")
+ else:
+ print(json_text, end="")
+
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/public/tcrb_lightcurve.php b/public/tcrb_lightcurve.php
index 41056fe..5566630 100644
--- a/public/tcrb_lightcurve.php
+++ b/public/tcrb_lightcurve.php
@@ -108,8 +108,9 @@ $summary = [
'max_mag' => null,
];
-if (empty($errors) && !empty($selectedFilters)) {
- $placeholders = implode(',', array_fill(0, count($selectedFilters), '?'));
+if (empty($errors) && !empty($availableFilters)) {
+ $queryFilters = array_column($availableFilters, 'code');
+ $placeholders = implode(',', array_fill(0, count($queryFilters), '?'));
$stmt = $pdo->prepare("
SELECT
`observed_at_utc`,
@@ -123,7 +124,7 @@ if (empty($errors) && !empty($selectedFilters)) {
WHERE `filter_code` IN ({$placeholders})
ORDER BY `observed_at_utc` ASC, `jd` ASC
");
- $stmt->execute($selectedFilters);
+ $stmt->execute($queryFilters);
$rows = $stmt->fetchAll();
foreach ($rows as $row) {
@@ -155,23 +156,33 @@ 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',
-];
+$filterMetadata = [];
+$filterMetadataPath = __DIR__ . '/../config/filter_colors.json';
+if (is_file($filterMetadataPath)) {
+ $filterMetadataRaw = file_get_contents($filterMetadataPath);
+ $filterMetadataDecoded = is_string($filterMetadataRaw) ? json_decode($filterMetadataRaw, true) : null;
+ if (is_array($filterMetadataDecoded)) {
+ $filterMetadata = $filterMetadataDecoded;
+ }
+}
+
+$filterPalette = [];
+$filterDescriptions = [];
+foreach ($filterMetadata as $filterCode => $filterConfig) {
+ if (!is_array($filterConfig)) {
+ continue;
+ }
+
+ $filterPalette[$filterCode] = (string) ($filterConfig['color'] ?? '');
+ $filterDescriptions[$filterCode] = (string) ($filterConfig['description'] ?? $filterCode);
+}
$chartDatasets = [];
-foreach ($selectedFilters as $selectedFilter) {
- $chartDatasets[$selectedFilter] = [
- 'filter' => $selectedFilter,
- 'color' => $filterPalette[$selectedFilter] ?? sprintf('#%06X', crc32($selectedFilter) & 0xFFFFFF),
+foreach ($availableFilters as $availableFilter) {
+ $filterCode = $availableFilter['code'];
+ $chartDatasets[$filterCode] = [
+ 'filter' => $filterCode,
+ 'color' => $filterPalette[$filterCode] ?? sprintf('#%06X', crc32($filterCode) & 0xFFFFFF),
'points' => [],
];
}
@@ -247,74 +258,10 @@ foreach ($chartDatasets as $dataset) {
];
}
-$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
'
- . 'Magnitude %{y:.3f}
'
- . 'UTC %{x}
'
- . 'Fenster %{customdata[0]} Punkte