Monatsvorschau weiter gebaut
This commit is contained in:
@@ -12,10 +12,12 @@ $currentYear = (int) date('Y');
|
||||
$selectedYear = isset($_GET['year']) ? (int) $_GET['year'] : $currentYear;
|
||||
$selectedMonth = isset($_GET['month']) ? max(1, min(12, (int) $_GET['month'])) : (int) date('n');
|
||||
$starDatabaseMagnitudeLimit = 6.0;
|
||||
$starDisplayMagnitudeLimit = 4.6;
|
||||
$starDisplayMagnitudeLimit = 4.4;
|
||||
$starLabelMagnitudeLimit = 2.0;
|
||||
$messierDatabaseMagnitudeLimit = 4.0;
|
||||
$messierDisplayMagnitudeLimit = 4.0;
|
||||
$messierDisplayMagnitudeLimit = 1.0;
|
||||
$moonPlanetMaxSeparationDeg = 1.5;
|
||||
$moonOccultationMaxMag = 2.0;
|
||||
|
||||
$years = range($currentYear - 2, $currentYear + 5);
|
||||
$months = [
|
||||
@@ -51,8 +53,33 @@ $starData = [];
|
||||
$lineStarData = [];
|
||||
$constellationLines = [];
|
||||
$messierData = [];
|
||||
$planetData = [];
|
||||
$moonData = null;
|
||||
$moonPhaseEvents = [];
|
||||
$moonPlanetApproaches = [];
|
||||
$moonOccultations = [];
|
||||
$monthEventList = [];
|
||||
$starDataError = null;
|
||||
|
||||
if (!function_exists('buildMonthOccultationStarLabel')) {
|
||||
function buildMonthOccultationStarLabel(array $row): string
|
||||
{
|
||||
$proper = trim((string) ($row['proper'] ?? ''));
|
||||
if ($proper !== '') {
|
||||
return $proper;
|
||||
}
|
||||
|
||||
$bayer = trim((string) ($row['bayer'] ?? ''));
|
||||
$con = trim((string) ($row['con'] ?? ''));
|
||||
if ($bayer !== '') {
|
||||
return $con !== '' ? $bayer . ' ' . $con : $bayer;
|
||||
}
|
||||
|
||||
$hip = isset($row['hip']) ? (int) $row['hip'] : 0;
|
||||
return $hip > 0 ? 'HIP ' . $hip : 'Unbenannter Stern';
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$dbConfig = require __DIR__ . '/../config/database.php';
|
||||
$dsn = sprintf(
|
||||
@@ -230,6 +257,221 @@ $chartLocalTime = new DateTimeImmutable(
|
||||
);
|
||||
$chartUtcTime = $chartLocalTime->setTimezone(new DateTimeZone('UTC'));
|
||||
|
||||
$pythonExecutable = PHP_OS_FAMILY === 'Windows' ? 'python' : '/usr/bin/python';
|
||||
$pythonScriptPath = __DIR__ . '/py/api.py';
|
||||
$pythonOutput = [];
|
||||
$pythonExitCode = 0;
|
||||
$planetErrorText = null;
|
||||
|
||||
$pythonArgs = [
|
||||
$pythonScriptPath,
|
||||
'month_sky_context',
|
||||
(string) $chartLatitude,
|
||||
(string) $chartLongitude,
|
||||
(string) $chartElevation,
|
||||
(string) $selectedYear,
|
||||
(string) $selectedMonth,
|
||||
(string) $monthTime['hour'],
|
||||
(string) $monthTime['minute'],
|
||||
$chartTimezone,
|
||||
];
|
||||
|
||||
$pythonCommand = $pythonExecutable . ' ' . implode(' ', array_map('escapeshellarg', $pythonArgs)) . ' 2>&1';
|
||||
exec($pythonCommand, $pythonOutput, $pythonExitCode);
|
||||
$pythonRaw = trim(implode("\n", $pythonOutput));
|
||||
$pythonDecoded = json_decode($pythonRaw, true);
|
||||
|
||||
if (is_array($pythonDecoded) && ($pythonDecoded['ok'] ?? false)) {
|
||||
if (is_array($pythonDecoded['bodies']['moon'] ?? null)) {
|
||||
$moon = $pythonDecoded['bodies']['moon'];
|
||||
$moonData = [
|
||||
'label' => (string) ($moon['label'] ?? 'Mond'),
|
||||
'az' => isset($moon['azimuth_deg']) ? (float) $moon['azimuth_deg'] : null,
|
||||
'alt' => isset($moon['altitude_deg']) ? (float) $moon['altitude_deg'] : null,
|
||||
'visible' => !empty($moon['visible']),
|
||||
'phaseFraction' => isset($moon['phase_fraction']) ? (float) $moon['phase_fraction'] : null,
|
||||
'waxing' => isset($moon['waxing']) ? (bool) $moon['waxing'] : null,
|
||||
];
|
||||
}
|
||||
foreach (($pythonDecoded['bodies']['planets'] ?? []) as $planet) {
|
||||
if (!is_array($planet)) {
|
||||
continue;
|
||||
}
|
||||
$planetData[] = [
|
||||
'key' => (string) ($planet['key'] ?? ''),
|
||||
'label' => (string) ($planet['label'] ?? ''),
|
||||
'az' => isset($planet['azimuth_deg']) ? (float) $planet['azimuth_deg'] : null,
|
||||
'alt' => isset($planet['altitude_deg']) ? (float) $planet['altitude_deg'] : null,
|
||||
'mag' => isset($planet['magnitude']) ? (float) $planet['magnitude'] : null,
|
||||
'visible' => !empty($planet['visible']),
|
||||
];
|
||||
}
|
||||
} elseif ($pythonRaw !== '') {
|
||||
$planetErrorText = $pythonRaw;
|
||||
}
|
||||
|
||||
$moonPhaseOutput = [];
|
||||
$moonPhaseExitCode = 0;
|
||||
$moonPhaseArgs = [
|
||||
$pythonScriptPath,
|
||||
'moon_phases_for_month',
|
||||
(string) $selectedYear,
|
||||
(string) $selectedMonth,
|
||||
$chartTimezone,
|
||||
];
|
||||
$moonPhaseCommand = $pythonExecutable . ' ' . implode(' ', array_map('escapeshellarg', $moonPhaseArgs)) . ' 2>&1';
|
||||
exec($moonPhaseCommand, $moonPhaseOutput, $moonPhaseExitCode);
|
||||
$moonPhaseRaw = trim(implode("\n", $moonPhaseOutput));
|
||||
$moonPhaseDecoded = json_decode($moonPhaseRaw, true);
|
||||
if (is_array($moonPhaseDecoded) && ($moonPhaseDecoded['ok'] ?? false)) {
|
||||
$moonPhaseEvents = is_array($moonPhaseDecoded['phases'] ?? null) ? $moonPhaseDecoded['phases'] : [];
|
||||
}
|
||||
|
||||
$moonPlanetOutput = [];
|
||||
$moonPlanetExitCode = 0;
|
||||
$moonPlanetArgs = [
|
||||
$pythonScriptPath,
|
||||
'moon_planet_approaches',
|
||||
(string) $chartLatitude,
|
||||
(string) $chartLongitude,
|
||||
(string) $chartElevation,
|
||||
(string) $selectedYear,
|
||||
(string) $selectedMonth,
|
||||
$chartTimezone,
|
||||
(string) $moonPlanetMaxSeparationDeg,
|
||||
];
|
||||
$moonPlanetCommand = $pythonExecutable . ' ' . implode(' ', array_map('escapeshellarg', $moonPlanetArgs)) . ' 2>&1';
|
||||
exec($moonPlanetCommand, $moonPlanetOutput, $moonPlanetExitCode);
|
||||
$moonPlanetRaw = trim(implode("\n", $moonPlanetOutput));
|
||||
$moonPlanetDecoded = json_decode($moonPlanetRaw, true);
|
||||
if (is_array($moonPlanetDecoded) && ($moonPlanetDecoded['ok'] ?? false)) {
|
||||
$moonPlanetApproaches = is_array($moonPlanetDecoded['approaches'] ?? null) ? $moonPlanetDecoded['approaches'] : [];
|
||||
}
|
||||
|
||||
$moonOccultationStars = [];
|
||||
try {
|
||||
if (isset($pdo)) {
|
||||
$stmtOccultStars = $pdo->prepare(
|
||||
"SELECT hip, proper, bayer, con, mag, rarad, decrad, dist
|
||||
FROM star_hipparcos
|
||||
WHERE rarad IS NOT NULL
|
||||
AND decrad IS NOT NULL
|
||||
AND mag IS NOT NULL
|
||||
AND mag <= :max_mag
|
||||
ORDER BY mag ASC, proper ASC, bayer ASC, hip ASC"
|
||||
);
|
||||
$stmtOccultStars->execute([':max_mag' => $moonOccultationMaxMag]);
|
||||
|
||||
foreach ($stmtOccultStars->fetchAll() as $row) {
|
||||
$moonOccultationStars[] = [
|
||||
'hip' => isset($row['hip']) ? (int) $row['hip'] : 0,
|
||||
'label' => buildMonthOccultationStarLabel($row),
|
||||
'constellation' => trim((string) ($row['con'] ?? '')),
|
||||
'mag' => isset($row['mag']) ? (float) $row['mag'] : 99.0,
|
||||
'ra' => isset($row['rarad']) ? (float) $row['rarad'] * (12 / M_PI) : 0.0,
|
||||
'dec' => isset($row['decrad']) ? (float) $row['decrad'] * (180 / M_PI) : 0.0,
|
||||
'distLy' => isset($row['dist']) && $row['dist'] !== null && (float) $row['dist'] > 0.0
|
||||
? (float) $row['dist'] * 3.26156
|
||||
: 1000.0,
|
||||
];
|
||||
}
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
}
|
||||
|
||||
if ($moonOccultationStars) {
|
||||
$tempOccultationFile = tempnam(sys_get_temp_dir(), 'month_occult_');
|
||||
if ($tempOccultationFile !== false) {
|
||||
try {
|
||||
file_put_contents($tempOccultationFile, json_encode($moonOccultationStars, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR));
|
||||
$moonOccultOutput = [];
|
||||
$moonOccultExitCode = 0;
|
||||
$moonOccultArgs = [
|
||||
$pythonScriptPath,
|
||||
'moon_star_occultations_for_month',
|
||||
(string) $chartLatitude,
|
||||
(string) $chartLongitude,
|
||||
(string) $chartElevation,
|
||||
(string) $selectedYear,
|
||||
(string) $selectedMonth,
|
||||
$chartTimezone,
|
||||
$tempOccultationFile,
|
||||
];
|
||||
$moonOccultCommand = $pythonExecutable . ' ' . implode(' ', array_map('escapeshellarg', $moonOccultArgs)) . ' 2>&1';
|
||||
exec($moonOccultCommand, $moonOccultOutput, $moonOccultExitCode);
|
||||
$moonOccultRaw = trim(implode("\n", $moonOccultOutput));
|
||||
$moonOccultDecoded = json_decode($moonOccultRaw, true);
|
||||
if (is_array($moonOccultDecoded) && ($moonOccultDecoded['ok'] ?? false)) {
|
||||
$moonOccultations = is_array($moonOccultDecoded['results'] ?? null) ? $moonOccultDecoded['results'] : [];
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
} finally {
|
||||
if (is_file($tempOccultationFile)) {
|
||||
@unlink($tempOccultationFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($moonPhaseEvents as $phaseEvent) {
|
||||
if (!is_array($phaseEvent)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$monthEventList[] = [
|
||||
'event' => (string) ($phaseEvent['label'] ?? ''),
|
||||
'date' => (string) ($phaseEvent['local_date'] ?? ''),
|
||||
'time' => (string) ($phaseEvent['local_time'] ?? ''),
|
||||
'local_iso' => (string) ($phaseEvent['local_iso'] ?? ''),
|
||||
'type' => 'moon_phase',
|
||||
];
|
||||
}
|
||||
|
||||
foreach ($moonPlanetApproaches as $approachEvent) {
|
||||
if (!is_array($approachEvent)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$distanceText = isset($approachEvent['separation_deg'])
|
||||
? number_format((float) $approachEvent['separation_deg'], 2, ',', '') . '°'
|
||||
: '';
|
||||
|
||||
$monthEventList[] = [
|
||||
'event' => trim((string) ($approachEvent['label'] ?? 'Mond nahe Planet') . ($distanceText !== '' ? ' (' . $distanceText . ')' : '')),
|
||||
'date' => (string) ($approachEvent['local_date'] ?? ''),
|
||||
'time' => (string) ($approachEvent['local_time'] ?? ''),
|
||||
'local_iso' => (string) ($approachEvent['local_iso'] ?? ''),
|
||||
'type' => 'moon_planet',
|
||||
];
|
||||
}
|
||||
|
||||
foreach ($moonOccultations as $occultationEvent) {
|
||||
if (!is_array($occultationEvent)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$maximum = is_array($occultationEvent['maximum'] ?? null) ? $occultationEvent['maximum'] : null;
|
||||
$star = is_array($occultationEvent['star'] ?? null) ? $occultationEvent['star'] : null;
|
||||
if ($maximum === null || $star === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$starLabel = (string) ($star['label'] ?? 'Stern');
|
||||
$starMag = isset($star['mag']) ? number_format((float) $star['mag'], 1, ',', '') : '';
|
||||
$monthEventList[] = [
|
||||
'event' => 'Mond bedeckt ' . $starLabel . ($starMag !== '' ? ' (mag ' . $starMag . ')' : ''),
|
||||
'date' => (string) ($maximum['local_date'] ?? ''),
|
||||
'time' => (string) ($maximum['local_time'] ?? ''),
|
||||
'local_iso' => (string) ($maximum['local_iso'] ?? ''),
|
||||
'type' => 'moon_occultation',
|
||||
];
|
||||
}
|
||||
|
||||
usort(
|
||||
$monthEventList,
|
||||
static fn (array $a, array $b): int => strcmp((string) ($a['local_iso'] ?? ''), (string) ($b['local_iso'] ?? ''))
|
||||
);
|
||||
|
||||
$chartConfig = [
|
||||
'monthName' => $months[$selectedMonth],
|
||||
'year' => $selectedYear,
|
||||
@@ -253,6 +495,8 @@ $starDataJson = json_encode($starData, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_S
|
||||
$lineStarDataJson = json_encode($lineStarData, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
$constellationLinesJson = json_encode($constellationLines, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
$messierDataJson = json_encode($messierData, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
$planetDataJson = json_encode($planetData, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
$moonDataJson = json_encode($moonData, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
$starDataErrorJson = json_encode($starDataError, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
?>
|
||||
|
||||
@@ -330,6 +574,90 @@ $starDataErrorJson = json_encode($starDataError, JSON_UNESCAPED_UNICODE | JSON_U
|
||||
<p id="monatskarteSouthStatus">Bereite Südhimmelkarte vor...</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="monatskarte-card">
|
||||
<div class="monatskarte-header">
|
||||
<div>
|
||||
<h3>Mondphasen <?= htmlspecialchars($months[$selectedMonth] . ' ' . (string) $selectedYear, ENT_QUOTES, 'UTF-8') ?></h3>
|
||||
<p>Chronologisch für <?= htmlspecialchars($chartTimezone, ENT_QUOTES, 'UTF-8') ?></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="monatskarte-footer">
|
||||
<?php if ($monthEventList): ?>
|
||||
<div class="riseset-grid" style="grid-template-columns: 1.3fr 1fr 1fr; row-gap: 10px;">
|
||||
<span class="riseset-label riseset-dim">Ereignis</span>
|
||||
<span class="riseset-label riseset-dim">Datum</span>
|
||||
<span class="riseset-label riseset-dim">Uhrzeit</span>
|
||||
<?php foreach ($monthEventList as $eventItem): ?>
|
||||
<span class="riseset-val"><?= htmlspecialchars((string) ($eventItem['event'] ?? ''), ENT_QUOTES, 'UTF-8') ?></span>
|
||||
<span class="riseset-val"><?= htmlspecialchars((string) ($eventItem['date'] ?? ''), ENT_QUOTES, 'UTF-8') ?></span>
|
||||
<span class="riseset-val"><?= htmlspecialchars((string) ($eventItem['time'] ?? ''), ENT_QUOTES, 'UTF-8') ?></span>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<p>Keine Mondphasen für diesen Monat gefunden.</p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="monatskarte-card">
|
||||
<div class="monatskarte-header">
|
||||
<div>
|
||||
<h3>Mond nahe Planeten</h3>
|
||||
<p>Maximalabstand <?= htmlspecialchars(number_format($moonPlanetMaxSeparationDeg, 1, ',', ''), ENT_QUOTES, 'UTF-8') ?>°</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="monatskarte-footer">
|
||||
<?php if ($moonPlanetApproaches): ?>
|
||||
<div class="riseset-grid" style="grid-template-columns: 1.5fr 1fr 1fr 1fr; row-gap: 10px;">
|
||||
<span class="riseset-label riseset-dim">Ereignis</span>
|
||||
<span class="riseset-label riseset-dim">Datum</span>
|
||||
<span class="riseset-label riseset-dim">Uhrzeit</span>
|
||||
<span class="riseset-label riseset-dim">Abstand</span>
|
||||
<?php foreach ($moonPlanetApproaches as $approachEvent): ?>
|
||||
<span class="riseset-val"><?= htmlspecialchars((string) ($approachEvent['label'] ?? ''), ENT_QUOTES, 'UTF-8') ?></span>
|
||||
<span class="riseset-val"><?= htmlspecialchars((string) ($approachEvent['local_date'] ?? ''), ENT_QUOTES, 'UTF-8') ?></span>
|
||||
<span class="riseset-val"><?= htmlspecialchars((string) ($approachEvent['local_time'] ?? ''), ENT_QUOTES, 'UTF-8') ?></span>
|
||||
<span class="riseset-val"><?= htmlspecialchars(number_format((float) ($approachEvent['separation_deg'] ?? 0), 2, ',', ''), ENT_QUOTES, 'UTF-8') ?>°</span>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<p>Keine Mond-Planet-Nähen innerhalb von <?= htmlspecialchars(number_format($moonPlanetMaxSeparationDeg, 1, ',', ''), ENT_QUOTES, 'UTF-8') ?>° gefunden.</p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="monatskarte-card">
|
||||
<div class="monatskarte-header">
|
||||
<div>
|
||||
<h3>Sternbedeckungen</h3>
|
||||
<p>Bis Grenzgröße <?= htmlspecialchars(number_format($moonOccultationMaxMag, 1, ',', ''), ENT_QUOTES, 'UTF-8') ?></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="monatskarte-footer">
|
||||
<?php if ($moonOccultations): ?>
|
||||
<div class="riseset-grid" style="grid-template-columns: 1.6fr 1fr 1fr 0.9fr; row-gap: 10px;">
|
||||
<span class="riseset-label riseset-dim">Stern</span>
|
||||
<span class="riseset-label riseset-dim">Datum</span>
|
||||
<span class="riseset-label riseset-dim">Uhrzeit</span>
|
||||
<span class="riseset-label riseset-dim">Mag</span>
|
||||
<?php foreach ($moonOccultations as $occultationEvent): ?>
|
||||
<?php $star = is_array($occultationEvent['star'] ?? null) ? $occultationEvent['star'] : []; ?>
|
||||
<?php $maximum = is_array($occultationEvent['maximum'] ?? null) ? $occultationEvent['maximum'] : []; ?>
|
||||
<span class="riseset-val"><?= htmlspecialchars((string) ($star['label'] ?? ''), ENT_QUOTES, 'UTF-8') ?></span>
|
||||
<span class="riseset-val"><?= htmlspecialchars((string) ($maximum['local_date'] ?? ''), ENT_QUOTES, 'UTF-8') ?></span>
|
||||
<span class="riseset-val"><?= htmlspecialchars((string) ($maximum['local_time'] ?? ''), ENT_QUOTES, 'UTF-8') ?></span>
|
||||
<span class="riseset-val"><?= htmlspecialchars(number_format((float) ($star['mag'] ?? 0), 1, ',', ''), ENT_QUOTES, 'UTF-8') ?></span>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<p>Keine Sternbedeckungen bis Magnitude <?= htmlspecialchars(number_format($moonOccultationMaxMag, 1, ',', ''), ENT_QUOTES, 'UTF-8') ?> gefunden.</p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -471,6 +799,8 @@ const MONTH_CHART_STARS = <?= $starDataJson ?: '[]' ?>;
|
||||
const MONTH_CHART_LINE_STARS = <?= $lineStarDataJson ?: '[]' ?>;
|
||||
const MONTH_CHART_CONSTELLATIONS = <?= $constellationLinesJson ?: '[]' ?>;
|
||||
const MONTH_CHART_MESSIER = <?= $messierDataJson ?: '[]' ?>;
|
||||
const MONTH_CHART_PLANETS = <?= $planetDataJson ?: '[]' ?>;
|
||||
const MONTH_CHART_MOON = <?= $moonDataJson ?: 'null' ?>;
|
||||
const MONTH_CHART_ERROR = <?= $starDataErrorJson ?: 'null' ?>;
|
||||
|
||||
(function () {
|
||||
@@ -636,6 +966,162 @@ const MONTH_CHART_ERROR = <?= $starDataErrorJson ?: 'null' ?>;
|
||||
};
|
||||
}
|
||||
|
||||
function placePlanetLabel(text, x, y, radius) {
|
||||
const attempts = [
|
||||
{ x: x + radius + 6, y: y - 1, align: 'left', baseline: 'middle' },
|
||||
{ x: x - radius - 6, y: y - 1, align: 'right', baseline: 'middle' },
|
||||
{ x, y: y - radius - 7, align: 'center', baseline: 'bottom' },
|
||||
{ x, y: y + radius + 7, align: 'center', baseline: 'top' },
|
||||
{ x: x + radius + 6, y: y - radius - 5, align: 'left', baseline: 'bottom' },
|
||||
{ x: x - radius - 6, y: y - radius - 5, align: 'right', baseline: 'bottom' },
|
||||
{ x: x + radius + 6, y: y + radius + 5, align: 'left', baseline: 'top' },
|
||||
{ x: x - radius - 6, y: y + radius + 5, align: 'right', baseline: 'top' },
|
||||
];
|
||||
|
||||
function drawLabelBackdrop(drawX, drawY, align, baseline) {
|
||||
ctx.save();
|
||||
ctx.font = '600 11px Rajdhani, sans-serif';
|
||||
const metrics = ctx.measureText(text);
|
||||
const textWidth = metrics.width;
|
||||
const ascent = metrics.actualBoundingBoxAscent || 8;
|
||||
const descent = metrics.actualBoundingBoxDescent || 4;
|
||||
const padX = 5;
|
||||
const padY = 3;
|
||||
|
||||
let boxX = drawX;
|
||||
if (align === 'center') boxX = drawX - textWidth / 2;
|
||||
else if (align === 'right') boxX = drawX - textWidth;
|
||||
|
||||
let boxY = drawY - ascent;
|
||||
if (baseline === 'middle') boxY = drawY - (ascent + descent) / 2;
|
||||
else if (baseline === 'bottom' || baseline === 'alphabetic') boxY = drawY - ascent - descent;
|
||||
|
||||
const rectX = boxX - padX;
|
||||
const rectY = boxY - padY;
|
||||
const rectW = textWidth + padX * 2;
|
||||
const rectH = ascent + descent + padY * 2;
|
||||
const radiusPx = 6;
|
||||
|
||||
ctx.fillStyle = 'rgba(3, 6, 12, 0.52)';
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(rectX + radiusPx, rectY);
|
||||
ctx.lineTo(rectX + rectW - radiusPx, rectY);
|
||||
ctx.quadraticCurveTo(rectX + rectW, rectY, rectX + rectW, rectY + radiusPx);
|
||||
ctx.lineTo(rectX + rectW, rectY + rectH - radiusPx);
|
||||
ctx.quadraticCurveTo(rectX + rectW, rectY + rectH, rectX + rectW - radiusPx, rectY + rectH);
|
||||
ctx.lineTo(rectX + radiusPx, rectY + rectH);
|
||||
ctx.quadraticCurveTo(rectX, rectY + rectH, rectX, rectY + rectH - radiusPx);
|
||||
ctx.lineTo(rectX, rectY + radiusPx);
|
||||
ctx.quadraticCurveTo(rectX, rectY, rectX + radiusPx, rectY);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
for (const attempt of attempts) {
|
||||
if (labelPlacer.drawText({
|
||||
text,
|
||||
x: attempt.x,
|
||||
y: attempt.y,
|
||||
font: '600 11px Rajdhani, sans-serif',
|
||||
color: 'rgba(246, 235, 214, 0.84)',
|
||||
align: attempt.align,
|
||||
baseline: attempt.baseline,
|
||||
padding: 2,
|
||||
})) {
|
||||
drawLabelBackdrop(attempt.x, attempt.y, attempt.align, attempt.baseline);
|
||||
ctx.save();
|
||||
ctx.font = '600 11px Rajdhani, sans-serif';
|
||||
ctx.textAlign = attempt.align;
|
||||
ctx.textBaseline = attempt.baseline;
|
||||
ctx.fillStyle = 'rgba(246, 235, 214, 0.84)';
|
||||
ctx.fillText(text, attempt.x, attempt.y);
|
||||
ctx.restore();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function placePlanetLabel(text, x, y, radius) {
|
||||
const attempts = [
|
||||
{ x: x + radius + 6, y: y - 1, align: 'left', baseline: 'middle' },
|
||||
{ x: x - radius - 6, y: y - 1, align: 'right', baseline: 'middle' },
|
||||
{ x, y: y - radius - 7, align: 'center', baseline: 'bottom' },
|
||||
{ x, y: y + radius + 7, align: 'center', baseline: 'top' },
|
||||
{ x: x + radius + 6, y: y - radius - 5, align: 'left', baseline: 'bottom' },
|
||||
{ x: x - radius - 6, y: y - radius - 5, align: 'right', baseline: 'bottom' },
|
||||
{ x: x + radius + 6, y: y + radius + 5, align: 'left', baseline: 'top' },
|
||||
{ x: x - radius - 6, y: y + radius + 5, align: 'right', baseline: 'top' },
|
||||
];
|
||||
|
||||
function drawLabelBackdrop(drawX, drawY, align, baseline) {
|
||||
ctx.save();
|
||||
ctx.font = '600 11px Rajdhani, sans-serif';
|
||||
const metrics = ctx.measureText(text);
|
||||
const textWidth = metrics.width;
|
||||
const ascent = metrics.actualBoundingBoxAscent || 8;
|
||||
const descent = metrics.actualBoundingBoxDescent || 4;
|
||||
const padX = 5;
|
||||
const padY = 3;
|
||||
|
||||
let boxX = drawX;
|
||||
if (align === 'center') boxX = drawX - textWidth / 2;
|
||||
else if (align === 'right') boxX = drawX - textWidth;
|
||||
|
||||
let boxY = drawY - ascent;
|
||||
if (baseline === 'middle') boxY = drawY - (ascent + descent) / 2;
|
||||
else if (baseline === 'bottom' || baseline === 'alphabetic') boxY = drawY - ascent - descent;
|
||||
|
||||
const rectX = boxX - padX;
|
||||
const rectY = boxY - padY;
|
||||
const rectW = textWidth + padX * 2;
|
||||
const rectH = ascent + descent + padY * 2;
|
||||
const radiusPx = 6;
|
||||
|
||||
ctx.fillStyle = 'rgba(3, 6, 12, 0.52)';
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(rectX + radiusPx, rectY);
|
||||
ctx.lineTo(rectX + rectW - radiusPx, rectY);
|
||||
ctx.quadraticCurveTo(rectX + rectW, rectY, rectX + rectW, rectY + radiusPx);
|
||||
ctx.lineTo(rectX + rectW, rectY + rectH - radiusPx);
|
||||
ctx.quadraticCurveTo(rectX + rectW, rectY + rectH, rectX + rectW - radiusPx, rectY + rectH);
|
||||
ctx.lineTo(rectX + radiusPx, rectY + rectH);
|
||||
ctx.quadraticCurveTo(rectX, rectY + rectH, rectX, rectY + rectH - radiusPx);
|
||||
ctx.lineTo(rectX, rectY + radiusPx);
|
||||
ctx.quadraticCurveTo(rectX, rectY, rectX + radiusPx, rectY);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
for (const attempt of attempts) {
|
||||
if (labelPlacer.drawText({
|
||||
text,
|
||||
x: attempt.x,
|
||||
y: attempt.y,
|
||||
font: '600 11px Rajdhani, sans-serif',
|
||||
color: 'rgba(246, 235, 214, 0.84)',
|
||||
align: attempt.align,
|
||||
baseline: attempt.baseline,
|
||||
padding: 2,
|
||||
})) {
|
||||
drawLabelBackdrop(attempt.x, attempt.y, attempt.align, attempt.baseline);
|
||||
ctx.save();
|
||||
ctx.font = '600 11px Rajdhani, sans-serif';
|
||||
ctx.textAlign = attempt.align;
|
||||
ctx.textBaseline = attempt.baseline;
|
||||
ctx.fillStyle = 'rgba(246, 235, 214, 0.84)';
|
||||
ctx.fillText(text, attempt.x, attempt.y);
|
||||
ctx.restore();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function resizeCanvas() {
|
||||
const cssSize = Math.max(320, Math.min(canvas.parentElement.clientWidth, 900));
|
||||
const ratio = Math.min(window.devicePixelRatio || 1, 2);
|
||||
@@ -933,6 +1419,168 @@ const MONTH_CHART_ERROR = <?= $starDataErrorJson ?: 'null' ?>;
|
||||
return name !== '' ? `M${messierId} ${name}` : `M${messierId}`;
|
||||
}
|
||||
|
||||
function getPlanetStyle(planet) {
|
||||
switch (String(planet.key || '')) {
|
||||
case 'mercury':
|
||||
return { fill: '#c9b9a7', glow: 'rgba(201, 185, 167, 0.34)' };
|
||||
case 'venus':
|
||||
return { fill: '#f4ddb2', glow: 'rgba(244, 221, 178, 0.4)' };
|
||||
case 'mars':
|
||||
return { fill: '#d7724c', glow: 'rgba(215, 114, 76, 0.34)' };
|
||||
case 'jupiter':
|
||||
return { fill: '#d9b07e', glow: 'rgba(217, 176, 126, 0.36)' };
|
||||
case 'saturn':
|
||||
return { fill: '#d8ca8b', glow: 'rgba(216, 202, 139, 0.34)' };
|
||||
case 'uranus':
|
||||
return { fill: '#8fd3df', glow: 'rgba(143, 211, 223, 0.3)' };
|
||||
case 'neptune':
|
||||
return { fill: '#5a78d6', glow: 'rgba(90, 120, 214, 0.3)' };
|
||||
default:
|
||||
return { fill: '#d8dde8', glow: 'rgba(216, 221, 232, 0.28)' };
|
||||
}
|
||||
}
|
||||
|
||||
function drawMoon(cx, cy, radius) {
|
||||
if (!MONTH_CHART_MOON) {
|
||||
return;
|
||||
}
|
||||
|
||||
const alt = Number(MONTH_CHART_MOON.alt);
|
||||
const az = Number(MONTH_CHART_MOON.az);
|
||||
if (!Number.isFinite(alt) || !Number.isFinite(az) || alt < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const pt = project(alt, az, radius, cx, cy);
|
||||
if (!pt) {
|
||||
return;
|
||||
}
|
||||
|
||||
const phaseFraction = clamp01(Number(MONTH_CHART_MOON.phaseFraction ?? 0.5));
|
||||
const waxing = Boolean(MONTH_CHART_MOON.waxing);
|
||||
const moonRadius = 10.5;
|
||||
const glow = ctx.createRadialGradient(pt.x, pt.y, 0, pt.x, pt.y, moonRadius * 2.1);
|
||||
glow.addColorStop(0, 'rgba(220, 226, 238, 0.28)');
|
||||
glow.addColorStop(0.5, 'rgba(220, 226, 238, 0.08)');
|
||||
glow.addColorStop(1, 'rgba(220, 226, 238, 0)');
|
||||
ctx.fillStyle = glow;
|
||||
ctx.beginPath();
|
||||
ctx.arc(pt.x, pt.y, moonRadius * 2.1, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
|
||||
ctx.save();
|
||||
ctx.translate(pt.x, pt.y);
|
||||
ctx.beginPath();
|
||||
ctx.arc(0, 0, moonRadius, 0, Math.PI * 2);
|
||||
ctx.clip();
|
||||
|
||||
ctx.fillStyle = '#515760';
|
||||
ctx.beginPath();
|
||||
ctx.arc(0, 0, moonRadius, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
|
||||
ctx.fillStyle = '#d6d9df';
|
||||
ctx.beginPath();
|
||||
ctx.arc(0, 0, moonRadius, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
|
||||
const phaseScale = 1 - 2 * phaseFraction;
|
||||
ctx.fillStyle = '#515760';
|
||||
ctx.beginPath();
|
||||
ctx.ellipse((waxing ? -1 : 1) * moonRadius * phaseScale, 0, moonRadius, moonRadius, 0, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
|
||||
ctx.restore();
|
||||
|
||||
ctx.strokeStyle = 'rgba(255, 255, 255, 0.3)';
|
||||
ctx.lineWidth = 1;
|
||||
ctx.beginPath();
|
||||
ctx.arc(pt.x, pt.y, moonRadius, 0, Math.PI * 2);
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
function drawPlanets(cx, cy, radius) {
|
||||
for (const planet of MONTH_CHART_PLANETS) {
|
||||
const alt = Number(planet.alt);
|
||||
const az = Number(planet.az);
|
||||
if (!Number.isFinite(alt) || !Number.isFinite(az) || alt < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const pt = project(alt, az, radius, cx, cy);
|
||||
if (!pt) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const style = getPlanetStyle(planet);
|
||||
const mag = Number(planet.mag);
|
||||
const baseRadius = Number.isFinite(mag)
|
||||
? Math.max(3.8, Math.min(8.6, 6.7 - mag * 0.72))
|
||||
: 5.4;
|
||||
const glowRadius = baseRadius * 2.2;
|
||||
|
||||
const glow = ctx.createRadialGradient(pt.x, pt.y, 0, pt.x, pt.y, glowRadius);
|
||||
glow.addColorStop(0, style.glow);
|
||||
glow.addColorStop(0.45, style.glow.replace(/0\.\d+\)$/, '0.12)'));
|
||||
glow.addColorStop(1, style.glow.replace(/0\.\d+\)$/, '0)'));
|
||||
ctx.fillStyle = glow;
|
||||
ctx.beginPath();
|
||||
ctx.arc(pt.x, pt.y, glowRadius, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
|
||||
ctx.fillStyle = style.fill;
|
||||
ctx.beginPath();
|
||||
ctx.arc(pt.x, pt.y, baseRadius, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
|
||||
ctx.strokeStyle = 'rgba(255, 255, 255, 0.26)';
|
||||
ctx.lineWidth = 1;
|
||||
ctx.beginPath();
|
||||
ctx.arc(pt.x, pt.y, baseRadius, 0, Math.PI * 2);
|
||||
ctx.stroke();
|
||||
|
||||
if (planet.key === 'jupiter') {
|
||||
ctx.save();
|
||||
ctx.translate(pt.x, pt.y);
|
||||
ctx.beginPath();
|
||||
ctx.arc(0, 0, baseRadius - 0.3, 0, Math.PI * 2);
|
||||
ctx.clip();
|
||||
|
||||
ctx.strokeStyle = 'rgba(116, 84, 54, 0.78)';
|
||||
ctx.lineWidth = Math.max(1, baseRadius * 0.34);
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(-baseRadius * 0.95, -baseRadius * 0.34);
|
||||
ctx.lineTo(baseRadius * 0.95, -baseRadius * 0.34);
|
||||
ctx.moveTo(-baseRadius * 0.95, baseRadius * 0.2);
|
||||
ctx.lineTo(baseRadius * 0.95, baseRadius * 0.2);
|
||||
ctx.stroke();
|
||||
|
||||
ctx.fillStyle = 'rgba(194, 92, 68, 0.95)';
|
||||
ctx.beginPath();
|
||||
ctx.ellipse(baseRadius * 0.42, baseRadius * 0.06, baseRadius * 0.3, baseRadius * 0.17, -0.18, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
if (planet.key === 'saturn') {
|
||||
ctx.save();
|
||||
ctx.translate(pt.x, pt.y);
|
||||
ctx.rotate(-0.35);
|
||||
ctx.strokeStyle = 'rgba(232, 221, 170, 0.72)';
|
||||
ctx.lineWidth = 1;
|
||||
ctx.beginPath();
|
||||
ctx.ellipse(0, 0, baseRadius * 1.7, baseRadius * 0.72, 0, 0, Math.PI * 2);
|
||||
ctx.stroke();
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
const label = String(planet.label || '').trim();
|
||||
if (label !== '') {
|
||||
placePlanetLabel(label, pt.x, pt.y, baseRadius);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function drawMessier(cx, cy, radius) {
|
||||
for (const obj of MONTH_CHART_MESSIER) {
|
||||
if (Number(obj.mag) > Number(MONTH_CHART_CONFIG.messierLimit)) {
|
||||
@@ -1099,6 +1747,8 @@ const MONTH_CHART_ERROR = <?= $starDataErrorJson ?: 'null' ?>;
|
||||
drawConstellations(cx, cy, radius);
|
||||
drawConstellationLabels(cx, cy, radius);
|
||||
drawMessier(cx, cy, radius);
|
||||
drawMoon(cx, cy, radius);
|
||||
drawPlanets(cx, cy, radius);
|
||||
drawStars(cx, cy, radius);
|
||||
|
||||
statusEl.textContent = 'Sternkarte fuer ' + MONTH_CHART_CONFIG.localTimeLabel;
|
||||
@@ -1248,6 +1898,36 @@ const MONTH_CHART_ERROR = <?= $starDataErrorJson ?: 'null' ?>;
|
||||
};
|
||||
}
|
||||
|
||||
function placePlanetLabel(text, x, y, radius) {
|
||||
const attempts = [
|
||||
{ x: x + radius + 6, y: y - 1, align: 'left', baseline: 'middle' },
|
||||
{ x: x - radius - 6, y: y - 1, align: 'right', baseline: 'middle' },
|
||||
{ x, y: y - radius - 7, align: 'center', baseline: 'bottom' },
|
||||
{ x, y: y + radius + 7, align: 'center', baseline: 'top' },
|
||||
{ x: x + radius + 6, y: y - radius - 5, align: 'left', baseline: 'bottom' },
|
||||
{ x: x - radius - 6, y: y - radius - 5, align: 'right', baseline: 'bottom' },
|
||||
{ x: x + radius + 6, y: y + radius + 5, align: 'left', baseline: 'top' },
|
||||
{ x: x - radius - 6, y: y + radius + 5, align: 'right', baseline: 'top' },
|
||||
];
|
||||
|
||||
for (const attempt of attempts) {
|
||||
if (labelPlacer.drawText({
|
||||
text,
|
||||
x: attempt.x,
|
||||
y: attempt.y,
|
||||
font: '600 11px Rajdhani, sans-serif',
|
||||
color: 'rgba(246, 235, 214, 0.84)',
|
||||
align: attempt.align,
|
||||
baseline: attempt.baseline,
|
||||
padding: 2,
|
||||
})) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function resizeCanvas() {
|
||||
const cssWidth = Math.max(420, Math.min(canvas.parentElement.clientWidth, 1100));
|
||||
const cssHeight = Math.round(cssWidth * 0.6);
|
||||
@@ -1414,6 +2094,158 @@ const MONTH_CHART_ERROR = <?= $starDataErrorJson ?: 'null' ?>;
|
||||
return name !== '' ? `M${messierId} ${name}` : `M${messierId}`;
|
||||
}
|
||||
|
||||
function getPlanetStyle(planet) {
|
||||
switch (String(planet.key || '')) {
|
||||
case 'mercury':
|
||||
return { fill: '#c9b9a7', glow: 'rgba(201, 185, 167, 0.34)' };
|
||||
case 'venus':
|
||||
return { fill: '#f4ddb2', glow: 'rgba(244, 221, 178, 0.4)' };
|
||||
case 'mars':
|
||||
return { fill: '#d7724c', glow: 'rgba(215, 114, 76, 0.34)' };
|
||||
case 'jupiter':
|
||||
return { fill: '#d9b07e', glow: 'rgba(217, 176, 126, 0.36)' };
|
||||
case 'saturn':
|
||||
return { fill: '#d8ca8b', glow: 'rgba(216, 202, 139, 0.34)' };
|
||||
case 'uranus':
|
||||
return { fill: '#8fd3df', glow: 'rgba(143, 211, 223, 0.3)' };
|
||||
case 'neptune':
|
||||
return { fill: '#5a78d6', glow: 'rgba(90, 120, 214, 0.3)' };
|
||||
default:
|
||||
return { fill: '#d8dde8', glow: 'rgba(216, 221, 232, 0.28)' };
|
||||
}
|
||||
}
|
||||
|
||||
function drawMoon(width, height) {
|
||||
if (!MONTH_CHART_MOON) return;
|
||||
|
||||
const alt = Number(MONTH_CHART_MOON.alt);
|
||||
const az = Number(MONTH_CHART_MOON.az);
|
||||
if (!Number.isFinite(alt) || !Number.isFinite(az) || alt < 0) return;
|
||||
|
||||
const pt = southProject(alt, az);
|
||||
if (!pt) return;
|
||||
|
||||
const phaseFraction = clamp01(Number(MONTH_CHART_MOON.phaseFraction ?? 0.5));
|
||||
const waxing = Boolean(MONTH_CHART_MOON.waxing);
|
||||
const moonRadius = 10.5;
|
||||
const glow = ctx.createRadialGradient(pt.x, pt.y, 0, pt.x, pt.y, moonRadius * 2.1);
|
||||
glow.addColorStop(0, 'rgba(220, 226, 238, 0.28)');
|
||||
glow.addColorStop(0.5, 'rgba(220, 226, 238, 0.08)');
|
||||
glow.addColorStop(1, 'rgba(220, 226, 238, 0)');
|
||||
ctx.fillStyle = glow;
|
||||
ctx.beginPath();
|
||||
ctx.arc(pt.x, pt.y, moonRadius * 2.1, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
|
||||
ctx.save();
|
||||
ctx.translate(pt.x, pt.y);
|
||||
ctx.beginPath();
|
||||
ctx.arc(0, 0, moonRadius, 0, Math.PI * 2);
|
||||
ctx.clip();
|
||||
|
||||
ctx.fillStyle = '#515760';
|
||||
ctx.beginPath();
|
||||
ctx.arc(0, 0, moonRadius, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
|
||||
ctx.fillStyle = '#d6d9df';
|
||||
ctx.beginPath();
|
||||
ctx.arc(0, 0, moonRadius, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
|
||||
const phaseScale = 1 - 2 * phaseFraction;
|
||||
ctx.fillStyle = '#515760';
|
||||
ctx.beginPath();
|
||||
ctx.ellipse((waxing ? -1 : 1) * moonRadius * phaseScale, 0, moonRadius, moonRadius, 0, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
|
||||
ctx.restore();
|
||||
|
||||
ctx.strokeStyle = 'rgba(255, 255, 255, 0.3)';
|
||||
ctx.lineWidth = 1;
|
||||
ctx.beginPath();
|
||||
ctx.arc(pt.x, pt.y, moonRadius, 0, Math.PI * 2);
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
function drawPlanets(width, height) {
|
||||
for (const planet of MONTH_CHART_PLANETS) {
|
||||
const alt = Number(planet.alt);
|
||||
const az = Number(planet.az);
|
||||
if (!Number.isFinite(alt) || !Number.isFinite(az) || alt < 0) continue;
|
||||
|
||||
const pt = southProject(alt, az);
|
||||
if (!pt) continue;
|
||||
|
||||
const style = getPlanetStyle(planet);
|
||||
const mag = Number(planet.mag);
|
||||
const baseRadius = Number.isFinite(mag)
|
||||
? Math.max(3.8, Math.min(8.6, 6.7 - mag * 0.72))
|
||||
: 5.4;
|
||||
const glowRadius = baseRadius * 2.2;
|
||||
|
||||
const glow = ctx.createRadialGradient(pt.x, pt.y, 0, pt.x, pt.y, glowRadius);
|
||||
glow.addColorStop(0, style.glow);
|
||||
glow.addColorStop(0.45, style.glow.replace(/0\.\d+\)$/, '0.12)'));
|
||||
glow.addColorStop(1, style.glow.replace(/0\.\d+\)$/, '0)'));
|
||||
ctx.fillStyle = glow;
|
||||
ctx.beginPath();
|
||||
ctx.arc(pt.x, pt.y, glowRadius, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
|
||||
ctx.fillStyle = style.fill;
|
||||
ctx.beginPath();
|
||||
ctx.arc(pt.x, pt.y, baseRadius, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
|
||||
ctx.strokeStyle = 'rgba(255, 255, 255, 0.26)';
|
||||
ctx.lineWidth = 1;
|
||||
ctx.beginPath();
|
||||
ctx.arc(pt.x, pt.y, baseRadius, 0, Math.PI * 2);
|
||||
ctx.stroke();
|
||||
|
||||
if (planet.key === 'jupiter') {
|
||||
ctx.save();
|
||||
ctx.translate(pt.x, pt.y);
|
||||
ctx.beginPath();
|
||||
ctx.arc(0, 0, baseRadius - 0.3, 0, Math.PI * 2);
|
||||
ctx.clip();
|
||||
|
||||
ctx.strokeStyle = 'rgba(116, 84, 54, 0.78)';
|
||||
ctx.lineWidth = Math.max(1, baseRadius * 0.34);
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(-baseRadius * 0.95, -baseRadius * 0.34);
|
||||
ctx.lineTo(baseRadius * 0.95, -baseRadius * 0.34);
|
||||
ctx.moveTo(-baseRadius * 0.95, baseRadius * 0.2);
|
||||
ctx.lineTo(baseRadius * 0.95, baseRadius * 0.2);
|
||||
ctx.stroke();
|
||||
|
||||
ctx.fillStyle = 'rgba(194, 92, 68, 0.95)';
|
||||
ctx.beginPath();
|
||||
ctx.ellipse(baseRadius * 0.42, baseRadius * 0.06, baseRadius * 0.3, baseRadius * 0.17, -0.18, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
if (planet.key === 'saturn') {
|
||||
ctx.save();
|
||||
ctx.translate(pt.x, pt.y);
|
||||
ctx.rotate(-0.35);
|
||||
ctx.strokeStyle = 'rgba(232, 221, 170, 0.72)';
|
||||
ctx.lineWidth = 1;
|
||||
ctx.beginPath();
|
||||
ctx.ellipse(0, 0, baseRadius * 1.7, baseRadius * 0.72, 0, 0, Math.PI * 2);
|
||||
ctx.stroke();
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
const label = String(planet.label || '').trim();
|
||||
if (label !== '') {
|
||||
placePlanetLabel(label, pt.x, pt.y, baseRadius);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function drawBackground(width, height) {
|
||||
const zenitPt = southProject(88, 180) || { x: width / 2, y: height * 0.18 };
|
||||
const horizonPt = southProject(0, 180) || { x: width / 2, y: height * 0.88 };
|
||||
@@ -1734,6 +2566,8 @@ const MONTH_CHART_ERROR = <?= $starDataErrorJson ?: 'null' ?>;
|
||||
drawConstellations(width, height);
|
||||
drawConstellationLabels(width, height);
|
||||
drawMessier(width, height);
|
||||
drawMoon(width, height);
|
||||
drawPlanets(width, height);
|
||||
drawStars(width, height);
|
||||
statusEl.textContent = 'Südhimmel für ' + MONTH_CHART_CONFIG.localTimeLabel;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user