Mondphase über den Monat drin
This commit is contained in:
@@ -1062,6 +1062,8 @@ $chartConfig = [
|
|||||||
],
|
],
|
||||||
'localTimeLabel' => $chartLocalTime->format('d.m.Y H:i') . ' ' . $chartTimezone,
|
'localTimeLabel' => $chartLocalTime->format('d.m.Y H:i') . ' ' . $chartTimezone,
|
||||||
'utcTimeIso' => $chartUtcTime->format(DateTimeInterface::ATOM),
|
'utcTimeIso' => $chartUtcTime->format(DateTimeInterface::ATOM),
|
||||||
|
'monthSkyHour' => $monthTime['hour'],
|
||||||
|
'monthSkyMinute' => $monthTime['minute'],
|
||||||
'starLimit' => $starDisplayMagnitudeLimit,
|
'starLimit' => $starDisplayMagnitudeLimit,
|
||||||
'starLabelLimit' => $starLabelMagnitudeLimit,
|
'starLabelLimit' => $starLabelMagnitudeLimit,
|
||||||
'messierLimit' => $messierDisplayMagnitudeLimit,
|
'messierLimit' => $messierDisplayMagnitudeLimit,
|
||||||
@@ -1074,6 +1076,7 @@ $constellationLinesJson = json_encode($constellationLines, JSON_UNESCAPED_UNICOD
|
|||||||
$messierDataJson = json_encode($messierData, 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);
|
$planetDataJson = json_encode($planetData, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||||
$moonDataJson = json_encode($moonData, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
$moonDataJson = json_encode($moonData, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||||
|
$moonPhaseEventsJson = json_encode($moonPhaseEvents, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||||
$starDataErrorJson = json_encode($starDataError, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
$starDataErrorJson = json_encode($starDataError, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||||
?>
|
?>
|
||||||
|
|
||||||
@@ -1152,6 +1155,25 @@ $starDataErrorJson = json_encode($starDataError, JSON_UNESCAPED_UNICODE | JSON_U
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section class="monatskarte-card">
|
||||||
|
<div class="monatskarte-header">
|
||||||
|
<div>
|
||||||
|
<h3>Mondphasen Im <?= htmlspecialchars($months[$selectedMonth] . ' ' . (string) $selectedYear, ENT_QUOTES, 'UTF-8') ?></h3>
|
||||||
|
<p>Eine Monatszeile vom ersten bis zum letzten Tag mit der Mondtextur aus <code>moon4geocron.png</code>.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="monatskarte-stage monatsphase-stage">
|
||||||
|
<div class="monatsphase-strip-wrap">
|
||||||
|
<canvas id="monatsphasenCanvas" width="1200" height="220" aria-label="Mondphasen des Monats in einer Zeile"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="monatskarte-footer">
|
||||||
|
<p id="monatsphasenStatus">Bereite Mondphasenleiste vor...</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<section class="monatskarte-card">
|
<section class="monatskarte-card">
|
||||||
<div class="monatskarte-header">
|
<div class="monatskarte-header">
|
||||||
<div>
|
<div>
|
||||||
@@ -1297,6 +1319,29 @@ $starDataErrorJson = json_encode($starDataError, JSON_UNESCAPED_UNICODE | JSON_U
|
|||||||
aspect-ratio: 5 / 3;
|
aspect-ratio: 5 / 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.monatsphase-stage {
|
||||||
|
padding-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monatsphase-strip-wrap {
|
||||||
|
width: 100%;
|
||||||
|
overflow-x: auto;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#monatsphasenCanvas {
|
||||||
|
display: block;
|
||||||
|
width: min(100%, 1200px);
|
||||||
|
min-width: 920px;
|
||||||
|
height: auto;
|
||||||
|
margin: 0 auto;
|
||||||
|
border-radius: 20px;
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at 50% 0%, rgba(92, 122, 186, 0.16), transparent 42%),
|
||||||
|
linear-gradient(180deg, rgba(3, 6, 12, 0.98) 0%, rgba(5, 9, 16, 0.99) 100%);
|
||||||
|
box-shadow: inset 0 0 0 1px rgba(140, 165, 220, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
.monatskarte-footer {
|
.monatskarte-footer {
|
||||||
padding-top: 0;
|
padding-top: 0;
|
||||||
}
|
}
|
||||||
@@ -1321,8 +1366,198 @@ const MONTH_CHART_CONSTELLATIONS = <?= $constellationLinesJson ?: '[]' ?>;
|
|||||||
const MONTH_CHART_MESSIER = <?= $messierDataJson ?: '[]' ?>;
|
const MONTH_CHART_MESSIER = <?= $messierDataJson ?: '[]' ?>;
|
||||||
const MONTH_CHART_PLANETS = <?= $planetDataJson ?: '[]' ?>;
|
const MONTH_CHART_PLANETS = <?= $planetDataJson ?: '[]' ?>;
|
||||||
const MONTH_CHART_MOON = <?= $moonDataJson ?: 'null' ?>;
|
const MONTH_CHART_MOON = <?= $moonDataJson ?: 'null' ?>;
|
||||||
|
const MONTH_PHASE_EVENTS = <?= $moonPhaseEventsJson ?: '[]' ?>;
|
||||||
const MONTH_CHART_ERROR = <?= $starDataErrorJson ?: 'null' ?>;
|
const MONTH_CHART_ERROR = <?= $starDataErrorJson ?: 'null' ?>;
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
const canvas = document.getElementById('monatsphasenCanvas');
|
||||||
|
const statusEl = document.getElementById('monatsphasenStatus');
|
||||||
|
|
||||||
|
if (!canvas || !statusEl || !window.Astronomy || !MONTH_CHART_CONFIG) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
const phaseImage = new Image();
|
||||||
|
phaseImage.src = <?= json_encode($publicBasePath . 'images/moons/moon4geocron.png', JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) ?>;
|
||||||
|
|
||||||
|
function clamp01(value) {
|
||||||
|
return Math.max(0, Math.min(1, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
function daysInMonth(year, month) {
|
||||||
|
return new Date(year, month, 0).getDate();
|
||||||
|
}
|
||||||
|
|
||||||
|
function phaseInfoForDay(year, month, day) {
|
||||||
|
const monthHour = Number(MONTH_CHART_CONFIG?.monthSkyHour ?? 12);
|
||||||
|
const monthMinute = Number(MONTH_CHART_CONFIG?.monthSkyMinute ?? 0);
|
||||||
|
const localPhaseTime = new Date(year, month - 1, day, monthHour, monthMinute, 0);
|
||||||
|
const phaseAngle = Number(Astronomy.MoonPhase(localPhaseTime));
|
||||||
|
const normalizedPhase = ((phaseAngle % 360) + 360) % 360;
|
||||||
|
|
||||||
|
return {
|
||||||
|
phaseAngleDeg: normalizedPhase,
|
||||||
|
illumination: clamp01((1 - Math.cos(normalizedPhase * Math.PI / 180)) / 2),
|
||||||
|
waxing: normalizedPhase <= 180,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function resizeCanvas() {
|
||||||
|
const cssWidth = Math.max(920, Math.floor(canvas.clientWidth || 1200));
|
||||||
|
const cssHeight = 220;
|
||||||
|
const dpr = Math.max(1, window.devicePixelRatio || 1);
|
||||||
|
|
||||||
|
canvas.width = Math.floor(cssWidth * dpr);
|
||||||
|
canvas.height = Math.floor(cssHeight * dpr);
|
||||||
|
canvas.style.height = cssHeight + 'px';
|
||||||
|
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
||||||
|
draw();
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawMoonDisc(x, y, radius, phaseInfo) {
|
||||||
|
const phaseRad = phaseInfo.phaseAngleDeg * Math.PI / 180;
|
||||||
|
const cosPhase = Math.cos(phaseRad);
|
||||||
|
|
||||||
|
ctx.save();
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(x, y, radius, 0, Math.PI * 2);
|
||||||
|
ctx.clip();
|
||||||
|
|
||||||
|
ctx.fillStyle = 'rgba(8, 10, 16, 0.96)';
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(x, y, radius, 0, Math.PI * 2);
|
||||||
|
ctx.fill();
|
||||||
|
|
||||||
|
ctx.globalAlpha = 0.96;
|
||||||
|
ctx.drawImage(phaseImage, x - radius, y - radius, radius * 2, radius * 2);
|
||||||
|
ctx.globalAlpha = 1;
|
||||||
|
|
||||||
|
ctx.fillStyle = 'rgba(8, 10, 16, 0.96)';
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(x, y, radius, 0, Math.PI * 2);
|
||||||
|
ctx.fill();
|
||||||
|
|
||||||
|
ctx.save();
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(x, y, radius, 0, Math.PI * 2);
|
||||||
|
ctx.clip();
|
||||||
|
|
||||||
|
if (phaseInfo.illumination >= 0.999) {
|
||||||
|
ctx.drawImage(phaseImage, x - radius, y - radius, radius * 2, radius * 2);
|
||||||
|
} else if (phaseInfo.illumination > 0.001) {
|
||||||
|
ctx.beginPath();
|
||||||
|
for (let i = 0; i <= 72; i++) {
|
||||||
|
const t = -radius + (2 * radius * i / 72);
|
||||||
|
const xr = Math.sqrt(Math.max(0, radius * radius - t * t));
|
||||||
|
const xt = (phaseInfo.waxing ? cosPhase : -cosPhase) * xr;
|
||||||
|
const px = x + xt;
|
||||||
|
const py = y + t;
|
||||||
|
if (i === 0) ctx.moveTo(px, py);
|
||||||
|
else ctx.lineTo(px, py);
|
||||||
|
}
|
||||||
|
for (let i = 72; i >= 0; i--) {
|
||||||
|
const t = -radius + (2 * radius * i / 72);
|
||||||
|
const xr = Math.sqrt(Math.max(0, radius * radius - t * t));
|
||||||
|
const px = x + (phaseInfo.waxing ? xr : -xr);
|
||||||
|
const py = y + t;
|
||||||
|
ctx.lineTo(px, py);
|
||||||
|
}
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.clip();
|
||||||
|
ctx.drawImage(phaseImage, x - radius, y - radius, radius * 2, radius * 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.restore();
|
||||||
|
ctx.restore();
|
||||||
|
|
||||||
|
ctx.strokeStyle = 'rgba(255, 255, 255, 0.16)';
|
||||||
|
ctx.lineWidth = 1;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(x, y, radius, 0, Math.PI * 2);
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
function draw() {
|
||||||
|
if (!phaseImage.complete) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const width = canvas.width / Math.max(1, window.devicePixelRatio || 1);
|
||||||
|
const height = canvas.height / Math.max(1, window.devicePixelRatio || 1);
|
||||||
|
const year = Number(MONTH_CHART_CONFIG.year);
|
||||||
|
const month = Number(MONTH_CHART_CONFIG.month);
|
||||||
|
const monthName = String(MONTH_CHART_CONFIG.monthName || '');
|
||||||
|
const totalDays = daysInMonth(year, month);
|
||||||
|
|
||||||
|
ctx.clearRect(0, 0, width, height);
|
||||||
|
|
||||||
|
const bg = ctx.createLinearGradient(0, 0, 0, height);
|
||||||
|
bg.addColorStop(0, '#02050a');
|
||||||
|
bg.addColorStop(1, '#04080f');
|
||||||
|
ctx.fillStyle = bg;
|
||||||
|
ctx.fillRect(0, 0, width, height);
|
||||||
|
|
||||||
|
ctx.fillStyle = 'rgba(240, 215, 132, 0.96)';
|
||||||
|
ctx.font = '600 16px Rajdhani, sans-serif';
|
||||||
|
ctx.textAlign = 'left';
|
||||||
|
ctx.textBaseline = 'top';
|
||||||
|
ctx.fillText('Mondphasen im ' + monthName + ' ' + year, 28, 18);
|
||||||
|
|
||||||
|
const leftPad = 22;
|
||||||
|
const rightPad = 22;
|
||||||
|
const usableWidth = width - leftPad - rightPad;
|
||||||
|
const spacing = usableWidth / totalDays;
|
||||||
|
const radius = Math.max(11, Math.min(18, spacing * 0.42));
|
||||||
|
const centerY = 82;
|
||||||
|
|
||||||
|
ctx.fillStyle = 'rgba(255, 255, 255, 0.72)';
|
||||||
|
ctx.font = '500 10px \"Share Tech Mono\", monospace';
|
||||||
|
ctx.textAlign = 'center';
|
||||||
|
ctx.textBaseline = 'top';
|
||||||
|
|
||||||
|
for (let day = 1; day <= totalDays; day++) {
|
||||||
|
const x = leftPad + spacing * (day - 0.5);
|
||||||
|
drawMoonDisc(x, centerY, radius, phaseInfoForDay(year, month, day));
|
||||||
|
ctx.fillText(String(day), x, centerY + radius + 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.font = '600 12px Rajdhani, sans-serif';
|
||||||
|
ctx.fillStyle = 'rgba(225, 232, 246, 0.94)';
|
||||||
|
|
||||||
|
for (const event of Array.isArray(MONTH_PHASE_EVENTS) ? MONTH_PHASE_EVENTS : []) {
|
||||||
|
const parts = String(event.local_date || '').split('.');
|
||||||
|
if (parts.length < 3) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const day = Number(parts[0]);
|
||||||
|
if (!Number.isFinite(day) || day < 1 || day > totalDays) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const x = leftPad + spacing * (day - 0.5);
|
||||||
|
ctx.fillText(String(event.label || ''), x, 158);
|
||||||
|
ctx.fillStyle = 'rgba(182, 196, 224, 0.94)';
|
||||||
|
ctx.fillText(day + '.' + month + '.', x, 175);
|
||||||
|
ctx.fillStyle = 'rgba(225, 232, 246, 0.94)';
|
||||||
|
}
|
||||||
|
|
||||||
|
statusEl.textContent = 'Mondphasenleiste fuer ' + monthName + ' ' + year;
|
||||||
|
}
|
||||||
|
|
||||||
|
phaseImage.onload = resizeCanvas;
|
||||||
|
phaseImage.onerror = function () {
|
||||||
|
statusEl.textContent = 'Mondgrafik konnte nicht geladen werden.';
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('resize', resizeCanvas);
|
||||||
|
|
||||||
|
if (phaseImage.complete) {
|
||||||
|
resizeCanvas();
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
(function () {
|
(function () {
|
||||||
const canvas = document.getElementById('monatskarteCanvas');
|
const canvas = document.getElementById('monatskarteCanvas');
|
||||||
const statusEl = document.getElementById('monatskarteStatus');
|
const statusEl = document.getElementById('monatskarteStatus');
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
# Monatsvorschau TODO
|
# Monatsvorschau TODO
|
||||||
|
|
||||||
- Meteorstroeme aufnehmen: aktiver Zeitraum, Maximum, geschaetzte Stundenrate
|
|
||||||
- Besonders enge Mond-Stern-Begegnungen heller Sterne ergaenzen, auch ohne Bedeckung
|
- Besonders enge Mond-Stern-Begegnungen heller Sterne ergaenzen, auch ohne Bedeckung
|
||||||
- Helle Stern-/Planeten-Begegnungen aufnehmen, z. B. Venus nahe Regulus oder Mars nahe Spica
|
- Helle Stern-/Planeten-Begegnungen aufnehmen, z. B. Venus nahe Regulus oder Mars nahe Spica
|
||||||
- Mond nahe Plejaden und Mond nahe Praesepe als eigene Ereignisse pruefen
|
- Mond nahe Plejaden und Mond nahe Praesepe als eigene Ereignisse pruefen
|
||||||
|
|||||||
Reference in New Issue
Block a user