This commit is contained in:
2026-05-06 19:51:05 +02:00
7 changed files with 1658 additions and 204 deletions
+5
View File
@@ -76,6 +76,10 @@
"label": "Sternenhimmel",
"menu_group": "Himmel & Planeten"
},
"drehbare_sternkarte.php": {
"label": "Drehbare Sternkarte",
"menu_group": "Himmel & Planeten"
},
"meteorshowers.php": {
"label": "Meteorstroeme",
"menu_group": "Himmel & Planeten"
@@ -132,6 +136,7 @@
"jupitersystem.php",
"tagbogen.php",
"sternenhimmel.php",
"drehbare_sternkarte.php",
"meteorshowers.php",
"geocron/index.php",
"geocron3d.php"
+1
View File
@@ -123,6 +123,7 @@ function manageablePublicPages(): array
'jupitersystem.php' => 'Jupitersystem',
'tagbogen.php' => 'Tagbogen',
'sternenhimmel.php' => 'Sternenhimmel',
'drehbare_sternkarte.php' => 'Drehbare Sternkarte',
'meteorshowers.php' => 'Meteorströme',
'geocron/index.php' => 'Geocron',
'geocron3d.php' => 'Geocron 3D',
+1 -1
View File
@@ -653,7 +653,7 @@ nav a.nav-logout:hover {
position: absolute;
top: calc(100% + 0.8rem);
right: 0;
width: 480px;
width: 620px;
max-width: 96vw;
display: flex;
flex-direction: column;
File diff suppressed because it is too large Load Diff
+36 -25
View File
@@ -138,36 +138,47 @@ if ($loggedIn && isset($_SESSION['user_id']) && $pdoHeader !== null) {
$menuGroups = [
[
'title' => 'Himmel & Planeten',
'title' => 'Mond & Finsternisse',
'links' => [
['href' => 'moonphase.php', 'label' => 'Mondphase'],
['href' => 'moonyear.php', 'label' => 'Mondphasen-Jahresübersicht'],
['href' => 'planetensichtbarkeit.php', 'label' => 'Planetensichtbarkeit'],
['href' => 'ephemeriden.php', 'label' => 'Ephemeriden'],
['href' => 'comets.php', 'label' => 'Kometen'],
['href' => 'astronomical_conversions.php', 'label' => 'Astronomical Conversions'],
['href' => 'tcrb_lightcurve.php', 'label' => 'T CrB Lichtkurve'],
['href' => 'solarsystem.php', 'label' => 'Sonnensystem'],
['href' => 'mars.php', 'label' => 'Mars'],
['href' => 'jupitersystem.php', 'label' => 'Jupitersystem'],
['href' => 'tagbogen.php', 'label' => 'Tagbogen'],
['href' => 'sternenhimmel.php', 'label' => 'Sternenhimmel'],
['href' => 'satellitenhimmel.php', 'label' => 'Satellitenhimmel'],
['href' => 'meteorshowers.php', 'label' => 'Meteorströme'],
['href' => 'asteroid_temp.php', 'label' => 'Asteroid-Temperatur'],
['href' => 'geocron/index.php', 'label' => 'Geocron'],
['href' => 'geocron3d.php', 'label' => 'Geocron 3D'],
['href' => 'geocron3d_C.php', 'label' => 'Geocron 3D Cesium'],
['href' => 'universesandbox.php', 'label' => 'Universe Sandbox'],
['href' => 'moonphase.php', 'label' => 'Mondphase'],
['href' => 'moonyear.php', 'label' => 'Mondphasen-Jahresübersicht'],
['href' => 'mondfinsternis.php', 'label' => 'Mondfinsternis'],
['href' => 'sonnenfinsternis.php', 'label' => 'Sonnenfinsternis'],
['href' => 'bedeckung.php', 'label' => 'Bedeckungsphänomene'],
['href' => 'sternbedeckungen.php', 'label' => 'Mond-Stern-Bedeckungen'],
],
],
[
'title' => 'Finsternisse & Bedeckungen',
'title' => 'Planeten & Sonnensystem',
'links' => [
['href' => 'mondfinsternis.php', 'label' => 'Mondfinsternis'],
['href' => 'sonnenfinsternis.php', 'label' => 'Sonnenfinsternis'],
['href' => 'bedeckung.php', 'label' => 'Bedeckungsphänomene'],
['href' => 'sternbedeckungen.php', 'label' => 'Mond-Stern-Bedeckungen'],
['href' => 'planetensichtbarkeit.php', 'label' => 'Planetensichtbarkeit'],
['href' => 'ephemeriden.php', 'label' => 'Ephemeriden'],
['href' => 'solarsystem.php', 'label' => 'Sonnensystem'],
['href' => 'mars.php', 'label' => 'Mars'],
['href' => 'jupitersystem.php', 'label' => 'Jupitersystem'],
['href' => 'asteroid_temp.php', 'label' => 'Asteroid-Temperatur'],
],
],
[
'title' => 'Sternenhimmel & Karten',
'links' => [
['href' => 'sternenhimmel.php', 'label' => 'Sternenhimmel'],
['href' => 'drehbare_sternkarte.php', 'label' => 'Drehbare Sternkarte'],
['href' => 'satellitenhimmel.php', 'label' => 'Satellitenhimmel'],
['href' => 'tagbogen.php', 'label' => 'Tagbogen'],
['href' => 'meteorshowers.php', 'label' => 'Meteorströme'],
['href' => 'comets.php', 'label' => 'Kometen'],
],
],
[
'title' => 'Tools & Simulationen',
'links' => [
['href' => 'tcrb_lightcurve.php', 'label' => 'T CrB Lichtkurve'],
['href' => 'astronomical_conversions.php', 'label' => 'Astronomical Conversions'],
['href' => 'geocron/index.php', 'label' => 'Geocron'],
['href' => 'geocron3d.php', 'label' => 'Geocron 3D'],
['href' => 'geocron3d_C.php', 'label' => 'Geocron 3D Cesium'],
['href' => 'universesandbox.php', 'label' => 'Universe Sandbox'],
],
],
];
+364 -74
View File
@@ -888,80 +888,391 @@ if (!function_exists('monthForecastBuildNarrativeText')) {
static fn (array $event): bool => in_array((string) ($event['type'] ?? ''), ['moon_phase', 'golden_handle', 'moon_planet', 'moon_planet_approach', 'moon_deep_sky', 'moon_deep_sky_approach', 'moon_occultation', 'moon_apsis'], true),
10
);
// Jahreszahl aus Datum entfernen (z. B. "19.04.2026" → "19.04.")
$shortDate = static function (string $d): string {
return (string) preg_replace('/\.\d{4}$/', '.', $d);
};
if ($moonEvents !== []) {
// Chronologisch sortieren nach local_iso
$sortedMoonEvents = $moonEvents;
usort($sortedMoonEvents, static fn (array $a, array $b): int => strcmp(
(string) ($a['local_iso'] ?? $a['date'] ?? ''),
(string) ($b['local_iso'] ?? $b['date'] ?? '')
));
// Natürlichsprachliche Liste: "Vollmond ist am 01.05., Apogäum am 05.05. und Neumond am 16.05."
$moonParts = [];
foreach ($sortedMoonEvents as $me) {
$label = trim((string) ($me['event'] ?? ''));
$date = trim((string) ($me['date'] ?? ''));
if ($label === '' || $date === '') {
continue;
$phaseEvents = array_values(array_filter($sortedMoonEvents, static fn (array $e): bool => (string) ($e['type'] ?? '') === 'moon_phase'));
$nonPhaseEvents = array_values(array_filter($sortedMoonEvents, static fn (array $e): bool => (string) ($e['type'] ?? '') !== 'moon_phase'));
$moonSentences = [];
// 1. Mondphasen in einem natürlichen Satz zusammenfassen
if ($phaseEvents !== []) {
$phaseParts = [];
foreach ($phaseEvents as $pe) {
$rawLabel = trim((string) ($pe['event'] ?? ''));
$date = trim((string) ($pe['date'] ?? ''));
if ($rawLabel === '' || $date === '') {
continue;
}
// "Mond Erstes Viertel" → "Erstes Viertel"
$cleanLabel = (string) preg_replace('/^Mond\s+/', '', $rawLabel);
$phaseParts[] = $cleanLabel . ' am ' . $shortDate($date);
}
// "Mond bedeckt X" → "der Mond bedeckt X" für natürlicheren Satzbau
if (str_starts_with($label, 'Mond bedeckt ')) {
$label = 'der ' . $label;
if ($phaseParts !== []) {
$lastPhase = array_pop($phaseParts);
$phaseList = $phaseParts !== [] ? implode(', ', $phaseParts) . ' und ' . $lastPhase : $lastPhase;
$moonSentences[] = 'Die Mondphasen im Überblick: ' . $phaseList . '.';
}
$moonParts[] = ['label' => $label, 'date' => $date];
}
if ($moonParts !== []) {
$moonSentenceParts = [];
foreach ($moonParts as $idx => $part) {
if ($idx === 0) {
$moonSentenceParts[] = $part['label'] . ' ist am ' . $part['date'];
// 2. Apsis, Begegnungen und Goldener Henkel – nach Datum gruppiert
$byDate = [];
foreach ($nonPhaseEvents as $ne) {
$date = trim((string) ($ne['date'] ?? ''));
if ($date === '') {
continue;
}
$byDate[$date][] = $ne;
}
foreach ($byDate as $date => $dateEvents) {
$apsis = null;
$approaches = [];
$goldenHandle = null;
foreach ($dateEvents as $de) {
$type = (string) ($de['type'] ?? '');
if ($type === 'moon_apsis') {
$apsis = $de;
} elseif ($type === 'golden_handle') {
$goldenHandle = $de;
} else {
$moonSentenceParts[] = $part['label'] . ' am ' . $part['date'];
$approaches[] = $de;
}
}
$lastPart = array_pop($moonSentenceParts);
$moonSentence = $moonSentenceParts !== []
? implode(', ', $moonSentenceParts) . ' und ' . $lastPart
: $lastPart;
$paragraphs[] = $moonSentence . '.';
$sd = $shortDate($date);
if ($goldenHandle !== null) {
$moonSentences[] = 'Am ' . $sd . ' zeigt der Mond den Goldenen Henkel: Ein Gebirgskamm am Mondrand leuchtet bereits im Sonnenlicht, während das Tal dahinter noch im Schatten liegt – ein lohnender Anblick schon im kleinen Teleskop.';
}
if ($apsis !== null) {
$apsisLabel = trim((string) ($apsis['event'] ?? ''));
// Distanz aus dem Label extrahieren: "(404.960 km)"
$distanceMatch = [];
$distanceStr = '';
if (preg_match('/\(([0-9.,]+\s*km)\)\s*$/', $apsisLabel, $distanceMatch)) {
$distanceStr = ' mit nur ' . $distanceMatch[1];
}
$isPerigee = str_contains($apsisLabel, 'Erdnähe') || str_contains($apsisLabel, 'Perigäum');
if ($isPerigee) {
$apsisText = 'Am ' . $sd . ' erreicht der Mond sein Perigäum' . $distanceStr . ' – er steht der Erde in diesem Monat am nächsten';
} else {
$apsisText = 'Am ' . $sd . ' erreicht der Mond sein Apogäum' . $distanceStr . ' – er steht der Erde in diesem Monat am fernsten';
}
if ($approaches !== []) {
// Begegnungen natürlich formulieren: "und zieht dabei in nur X Grad Abstand an Y vorbei"
$approachFragments = [];
foreach ($approaches as $ap) {
$apLabel = trim((string) ($ap['event'] ?? ''));
$sepMatch = [];
$sepStr = '';
if (preg_match('/\(([0-9.,]+\s*Grad)\)/', $apLabel, $sepMatch)) {
$apLabel = trim((string) preg_replace('/\s*\([0-9.,]+\s*Grad\)/', '', $apLabel));
$sepStr = ' in nur ' . $sepMatch[1] . ' Abstand';
}
if (str_starts_with($apLabel, 'Mond nahe ')) {
$target = substr($apLabel, strlen('Mond nahe '));
$approachFragments[] = $sepStr . ' an ' . $target . ' vorbei';
} elseif (str_starts_with($apLabel, 'Mond bedeckt ')) {
$target = substr($apLabel, strlen('Mond bedeckt '));
$approachFragments[] = ' und bedeckt dabei ' . $target;
} else {
$approachFragments[] = $sepStr !== '' ? $sepStr . ' bei ' . $apLabel : ' bei ' . $apLabel;
}
}
$approachClause = count($approachFragments) === 1
? ' und zieht dabei' . $approachFragments[0]
: ' und zieht dabei' . implode(' sowie', $approachFragments);
$moonSentences[] = $apsisText . $approachClause . '.';
} else {
$moonSentences[] = $apsisText . '.';
}
} elseif ($approaches !== []) {
foreach ($approaches as $ap) {
$apLabel = trim((string) ($ap['event'] ?? ''));
$apDate = trim((string) ($ap['date'] ?? ''));
$sepMatch = [];
$sepStr = '';
if (preg_match('/\(([0-9.,]+\s*Grad)\)/', $apLabel, $sepMatch)) {
$apLabel = trim((string) preg_replace('/\s*\([0-9.,]+\s*Grad\)/', '', $apLabel));
$sepStr = ' in nur ' . $sepMatch[1] . ' Abstand';
}
$apSd = $shortDate($apDate !== '' ? $apDate : $date);
if (str_starts_with($apLabel, 'Mond nahe ')) {
$target = substr($apLabel, strlen('Mond nahe '));
$moonSentences[] = 'Am ' . $apSd . ' zieht der Mond' . $sepStr . ' an ' . $target . ' vorbei.';
} elseif (str_starts_with($apLabel, 'Mond bedeckt ')) {
$target = substr($apLabel, strlen('Mond bedeckt '));
$moonSentences[] = 'Am ' . $apSd . ' bedeckt der Mond ' . $target . '.';
} else {
$moonSentences[] = $apLabel . ' am ' . $apSd . ($sepStr !== '' ? ' – ' . ltrim($sepStr) : '') . '.';
}
}
}
}
if ($moonSentences !== []) {
$paragraphs[] = implode(' ', $moonSentences);
}
}
// Hilfsfunktion: natürlichen Satz aus einem Planeten-Event bauen
$buildPlanetSentence = static function (array $event) use ($shortDate): ?string {
$type = (string) ($event['type'] ?? '');
$label = trim((string) ($event['event'] ?? ''));
$date = trim((string) ($event['date'] ?? ''));
if ($label === '' || $date === '') {
return null;
}
$sd = $shortDate($date);
if ($type === 'planet_conjunction') {
$sepMatch = [];
$sep = '';
if (preg_match('/\(([0-9.,]+\s*Grad)\)/', $label, $sepMatch)) {
$sep = $sepMatch[1];
$label = trim((string) preg_replace('/\s*\([0-9.,]+\s*Grad\)/', '', $label));
}
if (preg_match('/^(.+?)\s+nahe\s+(.+)$/i', $label, $m)) {
$p1 = trim($m[1]);
$p2 = trim($m[2]);
$sepNote = $sep !== '' ? ' – der Abstand beträgt nur ' . $sep : '';
return 'Am ' . $sd . ' rücken ' . $p1 . ' und ' . $p2 . ' eng zusammen' . $sepNote . '.';
}
}
if ($type === 'golden_gate_of_ecliptic') {
$planet = '';
if (preg_match('/^(\S+)\s+im\s+Goldenen\s+Tor/i', $label, $m)) {
$planet = $m[1];
}
if ($planet !== '') {
return 'Am ' . $sd . ' durchquert ' . $planet . ' das Goldene Tor der Ekliptik und zieht dabei zwischen Hyaden und Plejaden hindurch – ein lohnender Anblick im Fernglas.';
}
}
if ($type === 'inner_planet_elongation') {
$sepMatch = [];
$sepStr = '';
if (preg_match('/\(([0-9.,]+\s*Grad)\)/', $label, $sepMatch)) {
$sepStr = ' (' . $sepMatch[1] . ')';
$label = trim((string) preg_replace('/\s*\([0-9.,]+\s*Grad\)/', '', $label));
}
$isWest = str_contains($label, 'westlich') || str_contains($label, 'West');
$planet = '';
if (preg_match('/^(\S+)\s+/', $label, $m)) {
$planet = $m[1];
}
$visibility = $isWest ? 'als Morgenstern am besten zu beobachten' : 'als Abendstern am besten zu beobachten';
return 'Am ' . $sd . ' erreicht ' . ($planet ?: 'der Planet') . ' seine größte Elongation' . $sepStr . ' und ist dann ' . $visibility . '.';
}
if ($type === 'venus_peak_magnitude') {
$magMatch = [];
$magStr = '';
if (preg_match('/\(([^)]+)\)/', $label, $magMatch)) {
$magStr = ' mit ' . $magMatch[1];
}
return 'Am ' . $sd . ' erreicht Venus ihre größte Helligkeit' . $magStr . ' – sie ist dann selbst in der Dämmerung kaum zu übersehen.';
}
if ($type === 'outer_planet_event') {
$planet = '';
if (preg_match('/^(\S+)\s+/', $label, $m)) {
$planet = $m[1];
}
if (str_contains($label, 'Opposition')) {
return 'Am ' . $sd . ' steht ' . ($planet ?: 'der Planet') . ' in Opposition – er geht bei Sonnenuntergang auf, ist die ganze Nacht sichtbar und leuchtet so hell wie selten.';
}
if (str_contains($label, 'Konjunktion')) {
return 'Am ' . $sd . ' steht ' . ($planet ?: 'der Planet') . ' in Konjunktion mit der Sonne und ist für einige Wochen nicht zu beobachten.';
}
if (str_contains($label, 'Quadratur')) {
return 'Am ' . $sd . ' befindet sich ' . ($planet ?: 'der Planet') . ' in Quadratur – er steht 90 Grad von der Sonne entfernt und ist in der ersten oder zweiten Nachthälfte gut sichtbar.';
}
}
if ($type === 'mercury_good_visibility') {
return 'Um den ' . $sd . ' zeigt sich Merkur günstig am Himmel und lässt sich in der Dämmerung tief über dem Horizont aufspüren.';
}
if ($type === 'planet_parade') {
return 'Am ' . $sd . ' sind mehrere Planeten gleichzeitig am Himmel zu sehen – eine seltene Planetenparade, die einen Blick Richtung Süden lohnt.';
}
// Fallback
return 'Am ' . $sd . ' ist zu beobachten: ' . $label . '.';
};
$buildPlanetMotionSentence = static function (array $event) use ($shortDate): ?string {
$type = (string) ($event['type'] ?? '');
$label = trim((string) ($event['event'] ?? ''));
$date = trim((string) ($event['date'] ?? ''));
if ($label === '' || $date === '') {
return null;
}
$sd = $shortDate($date);
if ($type === 'outer_planet_station') {
$planet = '';
$isRetrograde = str_contains($label, 'rückläufig') || str_contains($label, 'Rückläufig');
$isPrograde = str_contains($label, 'rechtläufig') || str_contains($label, 'Rechtläufig') || str_contains($label, 'direkt');
if (preg_match('/^(\S+)\s+/', $label, $m)) {
$planet = $m[1];
}
if ($isRetrograde) {
return 'Ab dem ' . $sd . ' läuft ' . ($planet ?: 'der Planet') . ' rückläufig – er bewegt sich scheinbar rückwärts vor dem Sternhintergrund, was seine Beobachtung besonders interessant macht.';
}
if ($isPrograde) {
return 'Ab dem ' . $sd . ' setzt ' . ($planet ?: 'der Planet') . ' seine normale Vorwärtsbewegung am Himmel wieder fort.';
}
return 'Am ' . $sd . ' wird ' . ($planet ?: 'der Planet') . ' stationär und ändert seine Bewegungsrichtung am Himmel.';
}
if ($type === 'planet_constellation_change') {
// Label-Format: "Mars wechselt vom Sternbild Wassermann in das Sternbild Fische"
if (preg_match('/^(.+?)\s+wechselt\s+vom\s+Sternbild\s+(.+?)\s+in\s+das\s+Sternbild\s+(.+)$/i', $label, $m)) {
$planet = trim($m[1]);
$from = trim($m[2]);
$to = trim($m[3]);
// Formulierung deterministisch variieren (nach erstem Buchstaben des Planetennamens)
$variant = ord($planet[0]) % 3;
if ($variant === 1) {
return 'Am ' . $sd . ' verlässt ' . $planet . ' das Sternbild ' . $from . ' und zieht weiter in das Sternbild ' . $to . '.';
}
if ($variant === 2) {
return 'Am ' . $sd . ' tritt ' . $planet . ' aus dem Sternbild ' . $from . ' in das Sternbild ' . $to . ' über.';
}
return 'Am ' . $sd . ' wechselt ' . $planet . ' vom Sternbild ' . $from . ' in das Sternbild ' . $to . '.';
}
// Fallback ohne "vom"-Angabe
if (preg_match('/^(.+?)\s+wechselt\s+(?:in\s+das\s+Sternbild\s+|ins?\s+(?:Sternbild\s+)?)(.+)$/i', $label, $m)) {
return 'Am ' . $sd . ' wechselt ' . trim($m[1]) . ' in das Sternbild ' . trim($m[2]) . '.';
}
}
return 'Am ' . $sd . ' ist zu beobachten: ' . $label . '.';
};
$planetObservationEvents = $findEvents(
$filteredEvents,
static fn (array $event): bool => in_array((string) ($event['type'] ?? ''), ['inner_planet_event', 'venus_peak_magnitude', 'outer_planet_event', 'planet_conjunction', 'planet_parade', 'mercury_good_visibility', 'golden_gate_of_ecliptic'], true),
5
);
if ($planetObservationEvents !== []) {
$planetIntro = $pickRandomText([
'Bei den Planeten lohnt sich vor allem der Blick auf',
'Planetarisch besonders interessant sind in diesem Monat',
'Bei den Planeten stechen in diesem Monat vor allem',
'Einen planetaren Schwerpunkt bilden in diesem Monat',
], 'Bei den Planeten lohnt sich vor allem der Blick auf');
$planetOutro = $pickRandomText([
'Diese Termine geben dem Monat seinen charakteristischen planetaren Akzent.',
'Damit bekommt der Monat einen klaren planetaren Schwerpunkt.',
'Solche Termine prägen den planetaren Charakter dieses Monats besonders deutlich.',
'Sie setzen im Monatsverlauf markante planetare Akzente.',
], 'Diese Termine geben dem Monat seinen charakteristischen planetaren Akzent.');
$paragraphs[] = $planetIntro . ' ' . $describeEvents($planetObservationEvents) . '. ' . $planetOutro;
$planetSentences = [];
foreach ($planetObservationEvents as $pe) {
$sentence = $buildPlanetSentence($pe);
if ($sentence !== null) {
$planetSentences[] = $sentence;
}
}
if ($planetSentences !== []) {
$paragraphs[] = implode(' ', $planetSentences);
}
}
$planetMotionEvents = $findEvents(
$filteredEvents,
static fn (array $event): bool => in_array((string) ($event['type'] ?? ''), ['outer_planet_station', 'planet_constellation_change'], true),
4
8
);
if ($planetMotionEvents !== []) {
$planetMotionIntro = $pickRandomText([
'Im Monatsverlauf zeigen sich bei den Planeten auch auffällige Positionswechsel.',
'Auch in ihrer Stellung am Himmel verändern sich die Planeten in diesem Monat sichtbar.',
'Neben den Beobachtungshighlights fallen bei den Planeten auch einige Positionswechsel auf.',
'Auch die Bewegung der Planeten vor dem Sternhintergrund macht sich in diesem Monat bemerkbar.',
], 'Im Monatsverlauf zeigen sich bei den Planeten auch auffaellige Positionswechsel.');
$paragraphs[] = $planetMotionIntro . ' Dazu gehören ' . $describeEvents($planetMotionEvents) . '.';
// Sternbild-Akkusativformen für natürliche Sätze ohne "Sternbild"-Wiederholung
$constellationAkk = [
'Widder' => 'den Widder', 'Stier' => 'den Stier', 'Zwillinge' => 'die Zwillinge',
'Krebs' => 'den Krebs', 'Löwe' => 'den Löwen', 'Jungfrau' => 'die Jungfrau',
'Waage' => 'die Waage', 'Skorpion' => 'den Skorpion', 'Schütze' => 'den Schützen',
'Steinbock' => 'den Steinbock', 'Wassermann' => 'den Wassermann', 'Fische' => 'die Fische',
'Walfisch' => 'den Walfisch', 'Orion' => 'den Orion', 'Fuhrmann' => 'den Fuhrmann',
'Stier' => 'den Stier', 'Kassiopeia' => 'die Kassiopeia', 'Andromeda' => 'die Andromeda',
'Perseus' => 'den Perseus', 'Pegasus' => 'den Pegasus', 'Leier' => 'die Leier',
'Schwan' => 'den Schwan', 'Adler' => 'den Adler', 'Schlange' => 'die Schlange',
'Schlangenträger' => 'den Schlangenträger', 'Herkules' => 'den Herkules',
'Bärenhüter' => 'den Bärenhüter', 'Großer Bär' => 'den Großen Bären',
'Drache' => 'den Drachen', 'Nördliche Krone' => 'die Nördliche Krone',
'Kleiner Löwe' => 'den Kleinen Löwen', 'Rabe' => 'den Raben', 'Becher' => 'den Becher',
'Wasserschlange' => 'die Wasserschlange', 'Eridanus' => 'den Eridanus',
'Hase' => 'den Hasen', 'Großer Hund' => 'den Großen Hund',
'Kleiner Hund' => 'den Kleinen Hund', 'Einhorn' => 'das Einhorn',
'Delfin' => 'den Delfin', 'Pfeil' => 'den Pfeil', 'Fuchs' => 'den Fuchs',
'Kepheus' => 'den Kepheus', 'Südlicher Fisch' => 'den Südlichen Fisch',
'Jagdhunde' => 'die Jagdhunde', 'Löwchen' => 'das Löwchen',
];
$akkusativ = static function (string $name) use ($constellationAkk): string {
return $constellationAkk[$name] ?? 'das Sternbild ' . $name;
};
$stationEvents = array_values(array_filter($planetMotionEvents, static fn (array $e): bool => (string) ($e['type'] ?? '') === 'outer_planet_station'));
$changeEvents = array_values(array_filter($planetMotionEvents, static fn (array $e): bool => (string) ($e['type'] ?? '') === 'planet_constellation_change'));
$motionSentences = [];
// Rückläufigkeits-Ereignisse einzeln als Satz
foreach ($stationEvents as $se) {
$sentence = $buildPlanetMotionSentence($se);
if ($sentence !== null) {
$motionSentences[] = $sentence;
}
}
// Sternbild-Wechsel: nach Planet gruppieren, kompakt als eine Aussage
if ($changeEvents !== []) {
$byPlanet = [];
$planetOrder = [];
foreach ($changeEvents as $ce) {
$label = trim((string) ($ce['event'] ?? ''));
$date = trim((string) ($ce['date'] ?? ''));
if (!preg_match('/^(.+?)\s+wechselt\s+vom\s+Sternbild\s+(.+?)\s+in\s+das\s+Sternbild\s+(.+)$/i', $label, $m)) {
continue;
}
$planet = trim($m[1]);
$to = trim($m[3]);
if (!isset($byPlanet[$planet])) {
$byPlanet[$planet] = [];
$planetOrder[] = $planet;
}
$byPlanet[$planet][] = ['date' => $date, 'to' => $to];
}
if ($byPlanet !== []) {
$parts = [];
foreach ($planetOrder as $planet) {
$moves = $byPlanet[$planet];
if (count($moves) === 1) {
$parts[] = $planet . ' am ' . $shortDate($moves[0]['date']) . ' in ' . $akkusativ($moves[0]['to']);
} else {
// Mehrfacher Wechsel desselben Planeten
$moveParts = [];
foreach ($moves as $move) {
$moveParts[] = 'am ' . $shortDate($move['date']) . ' in ' . $akkusativ($move['to']);
}
$lastMove = array_pop($moveParts);
$parts[] = $planet . ' ' . ($moveParts !== [] ? implode(', ', $moveParts) . ' und dann ' . $lastMove : $lastMove);
}
}
$lastPart = array_pop($parts);
$changeText = count($byPlanet) > 1
? implode(', ', $parts) . ' und ' . $lastPart
: $lastPart;
$motionSentences[] = 'Sternbildwechsel im Monatsverlauf: ' . $changeText . '.';
}
}
if ($motionSentences !== []) {
$paragraphs[] = implode(' ', $motionSentences);
}
}
$cometEvents = $findEvents(
@@ -3138,40 +3449,13 @@ async function composeExportCanvas(sourceCanvas, logoPosition, options) {
const caption = monthChartExportCaption();
if (showCaption && caption !== '') {
const padding = Math.round(exportCanvas.width * 0.024);
const fontSize = Math.max(26, Math.round(exportCanvas.width * 0.022));
const fontSize = Math.max(18, Math.round(exportCanvas.width * 0.015));
exportCtx.save();
exportCtx.font = '600 ' + fontSize + 'px Rajdhani, sans-serif';
exportCtx.textAlign = 'right';
exportCtx.textBaseline = 'bottom';
const metrics = exportCtx.measureText(caption);
const textWidth = metrics.width;
const ascent = metrics.actualBoundingBoxAscent || fontSize * 0.8;
const descent = metrics.actualBoundingBoxDescent || fontSize * 0.2;
const boxPadX = Math.round(fontSize * 0.45);
const boxPadY = Math.round(fontSize * 0.3);
const boxWidth = textWidth + boxPadX * 2;
const boxHeight = ascent + descent + boxPadY * 2;
const boxX = exportCanvas.width - padding - boxWidth;
const boxY = exportCanvas.height - padding - boxHeight;
const radius = Math.round(fontSize * 0.28);
exportCtx.fillStyle = 'rgba(3, 7, 14, 0.7)';
exportCtx.beginPath();
exportCtx.moveTo(boxX + radius, boxY);
exportCtx.lineTo(boxX + boxWidth - radius, boxY);
exportCtx.quadraticCurveTo(boxX + boxWidth, boxY, boxX + boxWidth, boxY + radius);
exportCtx.lineTo(boxX + boxWidth, boxY + boxHeight - radius);
exportCtx.quadraticCurveTo(boxX + boxWidth, boxY + boxHeight, boxX + boxWidth - radius, boxY + boxHeight);
exportCtx.lineTo(boxX + radius, boxY + boxHeight);
exportCtx.quadraticCurveTo(boxX, boxY + boxHeight, boxX, boxY + boxHeight - radius);
exportCtx.lineTo(boxX, boxY + radius);
exportCtx.quadraticCurveTo(boxX, boxY, boxX + radius, boxY);
exportCtx.closePath();
exportCtx.fill();
exportCtx.fillStyle = 'rgba(235, 242, 255, 0.96)';
exportCtx.fillText(caption, exportCanvas.width - padding - boxPadX, exportCanvas.height - padding - boxPadY);
exportCtx.fillText(caption, exportCanvas.width - padding, exportCanvas.height - padding);
exportCtx.restore();
}
@@ -3211,7 +3495,13 @@ function monthChartExportCaption() {
return '';
}
return 'Sternhimmel für ' + String(hour).padStart(2, '0') + ':' + String(minute).padStart(2, '0') + ' Uhr';
const parts = String(MONTH_CHART_CONFIG.localTimeLabel || '').split(' ');
const date = parts[0] || '';
const hh = String(hour).padStart(2, '0');
const mm = String(minute).padStart(2, '0');
return date !== ''
? 'Sternhimmel am ' + date + ' um ' + hh + ':' + mm + ' Uhr'
: 'Sternhimmel um ' + hh + ':' + mm + ' Uhr';
}
function formatGermanDate(date) {
-104
View File
@@ -15,8 +15,6 @@ $constellationDataError = null;
$starDataError = null;
$messierData = [];
$messierDataError = null;
$milkyWayData = null;
$milkyWayDataError = null;
$availableLocations = [];
$starDbMagLimit = 12.0;
@@ -236,16 +234,6 @@ try {
$messierDataError = $e->getMessage();
}
try {
$milkyWayPath = __DIR__ . '/js/mw.json';
if (is_file($milkyWayPath)) {
$milkyWayData = json_decode((string) file_get_contents($milkyWayPath), true, 512, JSON_THROW_ON_ERROR);
}
} catch (Throwable $e) {
$milkyWayData = null;
$milkyWayDataError = $e->getMessage();
}
$favCometDataJson = json_encode(
$favCometData ?? [],
JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
@@ -271,14 +259,6 @@ $messierDataErrorJson = json_encode(
$messierDataError,
JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
);
$milkyWayDataJson = json_encode(
$milkyWayData,
JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
);
$milkyWayDataErrorJson = json_encode(
$milkyWayDataError,
JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
);
?>
<?php require __DIR__ . '/header.php'; ?>
<?php
@@ -393,13 +373,6 @@ if (!$availableLocations) {
</div>
</div>
<div class="sp-row">
<span class="sp-label">Milchstraße</span>
<div class="sp-toggles">
<label class="sp-toggle"><input type="checkbox" id="cfgMilkyWayShow" checked><span class="sp-pill"></span><span class="sp-text">an</span></label>
</div>
</div>
<div class="sp-row">
<span class="sp-label">Sternbilder</span>
<div class="sp-toggles">
@@ -522,14 +495,6 @@ const CFG_DEFAULT = {
ecliptic: {
color: 'rgba(255,175,35,0.52)', width: 1.1, dash: [5,5]
},
milkyWay: {
fillSoft: 'rgba(180, 208, 255, 0.018)',
fillMain: 'rgba(210, 228, 255, 0.028)',
stroke: 'rgba(220, 235, 255, 0.035)',
glow: 'rgba(185, 210, 255, 0.10)',
blur: 16,
minPoints: 20
},
constellations: {
color: 'rgba(100,160,220,0.38)', width: 0.8, dash: [],
colorLabels: 'rgba(120,175,230,0.50)', fontLabels: '9px Rajdhani, sans-serif'
@@ -631,7 +596,6 @@ let showAltaz = false;
let showPlanets = true;
let showSunMoon = true;
let showEclip = true;
let showMilkyWay = true;
let showMessier = true;
let showConst = true;
let showComets = true;
@@ -654,8 +618,6 @@ const STAR_DATA_URL = 'sternenhimmel.php?stars_api=1';
const EMBEDDED_STAR_ERROR = <?php echo $starDataErrorJson ?: 'null'; ?>;
const EMBEDDED_MESSIER_DATA = <?php echo $messierDataJson ?: '[]'; ?>;
const EMBEDDED_MESSIER_ERROR = <?php echo $messierDataErrorJson ?: 'null'; ?>;
const EMBEDDED_MILKY_WAY_DATA = <?php echo $milkyWayDataJson ?: 'null'; ?>;
const EMBEDDED_MILKY_WAY_ERROR = <?php echo $milkyWayDataErrorJson ?: 'null'; ?>;
const MESSIER = Array.isArray(EMBEDDED_MESSIER_DATA) ? EMBEDDED_MESSIER_DATA : [];
const FAV_COMETS = <?php echo $favCometDataJson ?: '[]'; ?>;
let constellationData = EMBEDDED_CONSTELLATION_DATA;
@@ -674,66 +636,6 @@ function buildMStyle() {
}
let M_STYLE = buildMStyle();
function buildMilkyWayRings(data) {
if (!data || !Array.isArray(data.features)) return [];
const rings = [];
for (const feature of data.features) {
const geometry = feature && feature.geometry ? feature.geometry : null;
if (!geometry) continue;
const polygons = geometry.type === 'Polygon'
? [geometry.coordinates]
: geometry.type === 'MultiPolygon'
? geometry.coordinates
: [];
for (const polygon of polygons) {
if (!Array.isArray(polygon)) continue;
for (const rawRing of polygon) {
if (!Array.isArray(rawRing) || rawRing.length < CFG.milkyWay.minPoints) continue;
const coords = [];
let area = 0;
for (let i = 0; i < rawRing.length; i++) {
const point = rawRing[i];
if (!Array.isArray(point) || point.length < 2) continue;
const lon = Number(point[0]);
const lat = Number(point[1]);
if (!Number.isFinite(lon) || !Number.isFinite(lat)) continue;
coords.push([lon, lat]);
if (i < rawRing.length - 1) {
const next = rawRing[i + 1];
const lon2 = Array.isArray(next) ? Number(next[0]) : NaN;
const lat2 = Array.isArray(next) ? Number(next[1]) : NaN;
if (Number.isFinite(lon2) && Number.isFinite(lat2)) {
area += (lon * lat2) - (lon2 * lat);
}
}
}
if (coords.length < CFG.milkyWay.minPoints) continue;
const areaAbs = Math.abs(area / 2);
if (!Number.isFinite(areaAbs)) continue;
rings.push({
coords,
areaAbs,
});
}
}
}
rings.sort((a, b) => b.areaAbs - a.areaAbs);
return rings;
}
const MILKY_WAY_RINGS = buildMilkyWayRings(EMBEDDED_MILKY_WAY_DATA);
function getTime() { return liveMode ? new Date() : simTime; }
// ═══════════════════════════════════════════════════════════════════════════════
@@ -1968,7 +1870,6 @@ function render() {
ctx.fill();
ctx.clip();
if (showMilkyWay) drawMilkyWay(date);
if (showAltaz) drawAltazGrid();
if (showEq) drawEquatorialGrid(date);
if (showEclip) drawEcliptic(date);
@@ -2303,7 +2204,6 @@ spBind('cfgPlanetsShow', () => showPlanets, v => showPlanets = v);
spBind('cfgCometsShow', () => showComets, v => showComets = v);
spBind('cfgCometsLabels', () => labelComets, v => labelComets = v);
spBind('cfgEclipShow', () => showEclip, v => showEclip = v);
spBind('cfgMilkyWayShow', () => showMilkyWay, v => showMilkyWay = v);
spBind('cfgMessierShow', () => showMessier, v => showMessier = v);
spBind('cfgSunMoonShow', () => showSunMoon, v => showSunMoon = v);
@@ -2394,10 +2294,6 @@ if (EMBEDDED_MESSIER_ERROR) {
console.warn('Messier-Daten aus der Datenbank nicht geladen:', EMBEDDED_MESSIER_ERROR);
}
if (EMBEDDED_MILKY_WAY_ERROR) {
console.warn('Milchstraßen-Daten nicht geladen:', EMBEDDED_MILKY_WAY_ERROR);
}
function updateStarLoadingUi(done, total) {
if (!starLoadingOverlay || !starLoadingFill || !starLoadingStatus || !starLoadingNote) return;
const safeTotal = Math.max(1, total);