Mondphase eingebaut
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 8.0 KiB |
+166
-10
@@ -22,6 +22,14 @@ const ASTRONOMY_LIB_URL = (() => {
|
|||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
const MOON_IMAGE_URL = (() => {
|
||||||
|
try {
|
||||||
|
return new URL("./moon_small.png", import.meta.url).toString();
|
||||||
|
} catch (error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
class SternwarteCard extends HTMLElement {
|
class SternwarteCard extends HTMLElement {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
@@ -31,10 +39,13 @@ class SternwarteCard extends HTMLElement {
|
|||||||
};
|
};
|
||||||
this._pendingRender = false;
|
this._pendingRender = false;
|
||||||
this._astronomyLoadPromise = null;
|
this._astronomyLoadPromise = null;
|
||||||
|
this._moonTexture = null;
|
||||||
|
this._moonTexturePromise = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
this._ensureAstronomy();
|
this._ensureAstronomy();
|
||||||
|
this._ensureMoonTexture();
|
||||||
this._ensureClockTimer();
|
this._ensureClockTimer();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,6 +132,29 @@ class SternwarteCard extends HTMLElement {
|
|||||||
return this._astronomyLoadPromise;
|
return this._astronomyLoadPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_ensureMoonTexture() {
|
||||||
|
if (this._moonTexture) return Promise.resolve(this._moonTexture);
|
||||||
|
if (this._moonTexturePromise) return this._moonTexturePromise;
|
||||||
|
if (!MOON_IMAGE_URL) return Promise.resolve(null);
|
||||||
|
|
||||||
|
this._moonTexturePromise = new Promise((resolve) => {
|
||||||
|
const image = new Image();
|
||||||
|
image.addEventListener("load", () => {
|
||||||
|
this._moonTexture = image;
|
||||||
|
this._moonTexturePromise = null;
|
||||||
|
if (this._hass) this._requestRender();
|
||||||
|
resolve(image);
|
||||||
|
}, { once: true });
|
||||||
|
image.addEventListener("error", () => {
|
||||||
|
this._moonTexturePromise = null;
|
||||||
|
resolve(null);
|
||||||
|
}, { once: true });
|
||||||
|
image.src = MOON_IMAGE_URL;
|
||||||
|
});
|
||||||
|
|
||||||
|
return this._moonTexturePromise;
|
||||||
|
}
|
||||||
|
|
||||||
_requestRender() {
|
_requestRender() {
|
||||||
if (this._isScheduleEditing()) {
|
if (this._isScheduleEditing()) {
|
||||||
this._pendingRender = true;
|
this._pendingRender = true;
|
||||||
@@ -500,7 +534,7 @@ class SternwarteCard extends HTMLElement {
|
|||||||
const astronomy = this._astronomy();
|
const astronomy = this._astronomy();
|
||||||
const observer = this._astroObserver();
|
const observer = this._astroObserver();
|
||||||
if (!astronomy || !observer) {
|
if (!astronomy || !observer) {
|
||||||
return { altitude: Number.NEGATIVE_INFINITY, illumination: 0 };
|
return { altitude: Number.NEGATIVE_INFINITY, illumination: 0, phaseAngle: 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -508,16 +542,108 @@ class SternwarteCard extends HTMLElement {
|
|||||||
const equator = astronomy.Equator(bodyMoon, date, observer, true, true);
|
const equator = astronomy.Equator(bodyMoon, date, observer, true, true);
|
||||||
const horizon = astronomy.Horizon(date, observer, equator.ra, equator.dec, "normal");
|
const horizon = astronomy.Horizon(date, observer, equator.ra, equator.dec, "normal");
|
||||||
const illumination = astronomy.Illumination(bodyMoon, date);
|
const illumination = astronomy.Illumination(bodyMoon, date);
|
||||||
|
const phaseAngle = Number(astronomy.MoonPhase(date)) || 0;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
altitude: (Number(horizon?.altitude) || 0) * Math.PI / 180,
|
altitude: (Number(horizon?.altitude) || 0) * Math.PI / 180,
|
||||||
illumination: Math.max(0, Math.min(1, Number(illumination?.phase_fraction) || 0)),
|
illumination: Math.max(0, Math.min(1, Number(illumination?.phase_fraction) || 0)),
|
||||||
|
phaseAngle: ((phaseAngle % 360) + 360) % 360,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return { altitude: Number.NEGATIVE_INFINITY, illumination: 0 };
|
return { altitude: Number.NEGATIVE_INFINITY, illumination: 0, phaseAngle: 0 };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_moonVisualState() {
|
||||||
|
const moon = this._moonStateAt(new Date());
|
||||||
|
const phaseAngle = moon.phaseAngle ?? 0;
|
||||||
|
const waxing = phaseAngle <= 180;
|
||||||
|
const radians = phaseAngle * Math.PI / 180;
|
||||||
|
|
||||||
|
return {
|
||||||
|
illumination: Math.max(0, Math.min(1, moon.illumination || 0)),
|
||||||
|
illuminationPercent: Math.round((moon.illumination || 0) * 100),
|
||||||
|
waxing,
|
||||||
|
phaseAngle,
|
||||||
|
cosPhase: Math.cos(radians),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
_renderMoonPhaseDataUrl(moonVisual, size = 64) {
|
||||||
|
const texture = this._moonTexture;
|
||||||
|
if (!texture) return "";
|
||||||
|
|
||||||
|
const canvas = document.createElement("canvas");
|
||||||
|
canvas.width = size;
|
||||||
|
canvas.height = size;
|
||||||
|
const ctx = canvas.getContext("2d");
|
||||||
|
if (!ctx) return "";
|
||||||
|
|
||||||
|
const radius = size / 2 - 1;
|
||||||
|
const x = size / 2;
|
||||||
|
const y = size / 2;
|
||||||
|
const cosPhase = Number(moonVisual.cosPhase) || 0;
|
||||||
|
|
||||||
|
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.98;
|
||||||
|
ctx.drawImage(texture, 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 (moonVisual.illumination >= 0.999) {
|
||||||
|
ctx.drawImage(texture, x - radius, y - radius, radius * 2, radius * 2);
|
||||||
|
} else if (moonVisual.illumination > 0.001) {
|
||||||
|
ctx.beginPath();
|
||||||
|
for (let i = 0; i <= 72; i += 1) {
|
||||||
|
const t = -radius + (2 * radius * i) / 72;
|
||||||
|
const xr = Math.sqrt(Math.max(0, radius * radius - t * t));
|
||||||
|
const xt = (moonVisual.waxing ? cosPhase : -cosPhase) * xr;
|
||||||
|
if (i === 0) {
|
||||||
|
ctx.moveTo(x + xt, y + t);
|
||||||
|
} else {
|
||||||
|
ctx.lineTo(x + xt, y + t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let i = 72; i >= 0; i -= 1) {
|
||||||
|
const t = -radius + (2 * radius * i) / 72;
|
||||||
|
const xr = Math.sqrt(Math.max(0, radius * radius - t * t));
|
||||||
|
ctx.lineTo(x + (moonVisual.waxing ? xr : -xr), y + t);
|
||||||
|
}
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.clip();
|
||||||
|
ctx.drawImage(texture, x - radius, y - radius, radius * 2, radius * 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.restore();
|
||||||
|
ctx.restore();
|
||||||
|
|
||||||
|
ctx.strokeStyle = "rgba(255,255,255,0.18)";
|
||||||
|
ctx.lineWidth = 1;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(x, y, radius, 0, Math.PI * 2);
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
return canvas.toDataURL("image/png");
|
||||||
|
}
|
||||||
|
|
||||||
_moonTrackState() {
|
_moonTrackState() {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const cycleStart = new Date(now);
|
const cycleStart = new Date(now);
|
||||||
@@ -709,6 +835,8 @@ class SternwarteCard extends HTMLElement {
|
|||||||
}
|
}
|
||||||
const astro = this._astroState();
|
const astro = this._astroState();
|
||||||
const moonTrack = this._moonTrackState();
|
const moonTrack = this._moonTrackState();
|
||||||
|
const moonVisual = this._moonVisualState();
|
||||||
|
const moonImageDataUrl = this._renderMoonPhaseDataUrl(moonVisual, 56);
|
||||||
|
|
||||||
this.shadowRoot.innerHTML = `
|
this.shadowRoot.innerHTML = `
|
||||||
<style>
|
<style>
|
||||||
@@ -790,10 +918,6 @@ class SternwarteCard extends HTMLElement {
|
|||||||
outline: 2px solid var(--primary-color);
|
outline: 2px solid var(--primary-color);
|
||||||
outline-offset: 2px;
|
outline-offset: 2px;
|
||||||
}
|
}
|
||||||
.stat-spacer {
|
|
||||||
visibility: hidden;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
.stat.wind-good {
|
.stat.wind-good {
|
||||||
background: rgba(76, 175, 80, 0.14);
|
background: rgba(76, 175, 80, 0.14);
|
||||||
border-color: rgba(76, 175, 80, 0.3);
|
border-color: rgba(76, 175, 80, 0.3);
|
||||||
@@ -817,6 +941,28 @@ class SternwarteCard extends HTMLElement {
|
|||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
.moon-stat {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
.moon-icon {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
flex: 0 0 28px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: block;
|
||||||
|
object-fit: cover;
|
||||||
|
box-shadow: 0 0 10px rgba(246, 240, 196, 0.12);
|
||||||
|
}
|
||||||
|
.moon-percent {
|
||||||
|
text-align: right;
|
||||||
|
line-height: 1.05;
|
||||||
|
}
|
||||||
|
.moon-percent .stat-value {
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
.position {
|
.position {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
@@ -1096,9 +1242,6 @@ class SternwarteCard extends HTMLElement {
|
|||||||
.stats {
|
.stats {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
.stat-spacer {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.schedule-overview {
|
.schedule-overview {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
@@ -1129,7 +1272,20 @@ class SternwarteCard extends HTMLElement {
|
|||||||
<span class="stat-label">Wind</span>
|
<span class="stat-label">Wind</span>
|
||||||
<span class="stat-value">${windIndicator.display}</span>
|
<span class="stat-value">${windIndicator.display}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat stat-spacer" aria-hidden="true"></div>
|
<div class="stat">
|
||||||
|
<span class="stat-label">Mond</span>
|
||||||
|
<div class="moon-stat">
|
||||||
|
<img
|
||||||
|
class="moon-icon"
|
||||||
|
src="${moonImageDataUrl}"
|
||||||
|
alt="Mondphase"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<div class="moon-percent">
|
||||||
|
<span class="stat-value">${moonVisual.illuminationPercent} %</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="badges">
|
<div class="badges">
|
||||||
${this._boolBadge("Dach zu", this.config.roofClosed, "Ja", "Nein")}
|
${this._boolBadge("Dach zu", this.config.roofClosed, "Ja", "Nein")}
|
||||||
|
|||||||
Reference in New Issue
Block a user