Auf ae.js umgestellt

This commit is contained in:
2026-04-29 08:47:26 +02:00
parent 0a4ade5bdd
commit 35086a3441
2 changed files with 119 additions and 90 deletions
+3
View File
@@ -6,9 +6,12 @@ Lovelace-Card für die Sternwarte.
```text ```text
dist/ dist/
astronomy.browser.min.js
sternwarte-card.js sternwarte-card.js
``` ```
`sternwarte-card.js` lädt `astronomy.browser.min.js` automatisch nach, um Dämmerungszeiten und Mondposition präziser zu berechnen.
## Für HACS ## Für HACS
Dieser Ordner ist als Root eines späteren HACS-Dashboard-Repositorys vorbereitet. Dieser Ordner ist als Root eines späteren HACS-Dashboard-Repositorys vorbereitet.
+114 -88
View File
@@ -14,6 +14,14 @@ const DEFAULT_ENTITIES = {
astroLon: 11.0481766, astroLon: 11.0481766,
}; };
const ASTRONOMY_LIB_URL = (() => {
try {
return new URL("./astronomy.browser.min.js", import.meta.url).toString();
} catch (error) {
return null;
}
})();
class SternwarteCard extends HTMLElement { class SternwarteCard extends HTMLElement {
constructor() { constructor() {
super(); super();
@@ -22,9 +30,11 @@ class SternwarteCard extends HTMLElement {
close: { draft: "", editing: false, pending: false }, close: { draft: "", editing: false, pending: false },
}; };
this._pendingRender = false; this._pendingRender = false;
this._astronomyLoadPromise = null;
} }
connectedCallback() { connectedCallback() {
this._ensureAstronomy();
this._ensureClockTimer(); this._ensureClockTimer();
} }
@@ -71,6 +81,46 @@ class SternwarteCard extends HTMLElement {
}, 30000); }, 30000);
} }
_astronomy() {
return window.Astronomy || null;
}
_ensureAstronomy() {
const astronomy = this._astronomy();
if (astronomy) return Promise.resolve(astronomy);
if (this._astronomyLoadPromise) return this._astronomyLoadPromise;
if (!ASTRONOMY_LIB_URL) return Promise.resolve(null);
this._astronomyLoadPromise = new Promise((resolve) => {
const finish = () => {
const loaded = this._astronomy();
if (loaded && this._hass) {
this._requestRender();
}
this._astronomyLoadPromise = null;
resolve(loaded || null);
};
const existing = Array.from(document.querySelectorAll("script"))
.find((script) => script.src === ASTRONOMY_LIB_URL);
if (existing) {
existing.addEventListener("load", finish, { once: true });
existing.addEventListener("error", finish, { once: true });
return;
}
const script = document.createElement("script");
script.src = ASTRONOMY_LIB_URL;
script.async = true;
script.addEventListener("load", finish, { once: true });
script.addEventListener("error", finish, { once: true });
document.head.appendChild(script);
});
return this._astronomyLoadPromise;
}
_requestRender() { _requestRender() {
if (this._isScheduleEditing()) { if (this._isScheduleEditing()) {
this._pendingRender = true; this._pendingRender = true;
@@ -389,107 +439,83 @@ class SternwarteCard extends HTMLElement {
return date >= lastSunday(2, 1) && date < lastSunday(9, 1); return date >= lastSunday(2, 1) && date < lastSunday(9, 1);
} }
_calcTwilight(date) { _astroObserver() {
const astronomy = this._astronomy();
if (!astronomy) return null;
const lat = Number(this.config.astroLat); const lat = Number(this.config.astroLat);
const lon = Number(this.config.astroLon); const lon = Number(this.config.astroLon);
const jd = date.getTime() / 86400000 + 2440587.5; if (!Number.isFinite(lat) || !Number.isFinite(lon)) return null;
const n = jd - 2451545.0; return new astronomy.Observer(lat, lon, 0);
const L = ((280.460 + 0.9856474 * n) % 360 + 360) % 360; }
const g = ((357.528 + 0.9856003 * n) % 360 + 360) % 360;
const gR = g * Math.PI / 180;
const lam = L + 1.915 * Math.sin(gR) + 0.020 * Math.sin(2 * gR);
const lamR = lam * Math.PI / 180;
const eps = 23.439 - 0.0000004 * n;
const epsR = eps * Math.PI / 180;
const dec = Math.asin(Math.sin(epsR) * Math.sin(lamR));
const RA = Math.atan2(Math.cos(epsR) * Math.sin(lamR), Math.cos(lamR)) * 180 / Math.PI / 15;
const GMST0h = ((L + 180) % 360 + 360) % 360 / 15;
const solarNoon = (12 - lon / 15 - (RA - GMST0h + 12 + 720) % 24 + 24) % 24;
const latR = lat * Math.PI / 180;
const eventUTC = (depDeg) => { _astroTimeToUtcHour(eventTime) {
const cosH = (Math.sin(-depDeg * Math.PI / 180) - Math.sin(latR) * Math.sin(dec)) if (!eventTime) return null;
/ (Math.cos(latR) * Math.cos(dec)); const date = eventTime.date instanceof Date ? eventTime.date : null;
if (cosH < -1 || cosH > 1) return null; if (!date || Number.isNaN(date.getTime())) return null;
const H = Math.acos(cosH) * 180 / Math.PI / 15; return date.getUTCHours() + date.getUTCMinutes() / 60 + date.getUTCSeconds() / 3600;
return { dawn: (solarNoon - H + 24) % 24, dusk: (solarNoon + H) % 24 }; }
_calcTwilight(date) {
const astronomy = this._astronomy();
const observer = this._astroObserver();
if (!astronomy || !observer) {
return {
sun: { dawn: null, dusk: null },
civil: { dawn: null, dusk: null },
naut: { dawn: null, dusk: null },
astro: { dawn: null, dusk: null },
}; };
}
try {
const bodySun = astronomy.Body?.Sun ?? "Sun";
const start = new Date(date);
start.setHours(0, 0, 0, 0);
const limitDays = 2;
const altitudeEvent = (altitude) => ({
dawn: this._astroTimeToUtcHour(astronomy.SearchAltitude(bodySun, observer, +1, start, limitDays, altitude)),
dusk: this._astroTimeToUtcHour(astronomy.SearchAltitude(bodySun, observer, -1, start, limitDays, altitude)),
});
return { return {
sun: eventUTC(0.833), sun: {
civil: eventUTC(6), dawn: this._astroTimeToUtcHour(astronomy.SearchRiseSet(bodySun, observer, +1, start, limitDays, 0)),
naut: eventUTC(12), dusk: this._astroTimeToUtcHour(astronomy.SearchRiseSet(bodySun, observer, -1, start, limitDays, 0)),
astro: eventUTC(18), },
civil: altitudeEvent(-6),
naut: altitudeEvent(-12),
astro: altitudeEvent(-18),
};
} catch (error) {
return {
sun: { dawn: null, dusk: null },
civil: { dawn: null, dusk: null },
naut: { dawn: null, dusk: null },
astro: { dawn: null, dusk: null },
}; };
} }
_julianDate(date) {
return date.getTime() / 86400000 + 2440587.5;
}
_gmstHours(jd) {
const T = (jd - 2451545) / 36525;
const gmst = 280.46061837 + 360.98564736629 * (jd - 2451545) + 0.000387933 * T * T
- (T * T * T) / 38710000;
return (((gmst % 360) + 360) % 360) / 15;
} }
_moonStateAt(date) { _moonStateAt(date) {
const jd = this._julianDate(date); const astronomy = this._astronomy();
const d = jd - 2451543.5; const observer = this._astroObserver();
const lat = Number(this.config.astroLat) * Math.PI / 180; if (!astronomy || !observer) {
const lonHours = Number(this.config.astroLon) / 15; return { altitude: Number.NEGATIVE_INFINITY, illumination: 0 };
const toRad = (deg) => deg * Math.PI / 180; }
const normalizeDeg = (deg) => ((deg % 360) + 360) % 360;
const sunMeanLongitude = normalizeDeg(280.460 + 0.9856474 * (jd - 2451545.0)); try {
const sunMeanAnomaly = normalizeDeg(357.528 + 0.9856003 * (jd - 2451545.0)); const bodyMoon = astronomy.Body?.Moon ?? "Moon";
const sunLongitude = normalizeDeg( const equator = astronomy.Equator(bodyMoon, date, observer, true, true);
sunMeanLongitude + 1.915 * Math.sin(toRad(sunMeanAnomaly)) + 0.020 * Math.sin(2 * toRad(sunMeanAnomaly)), const horizon = astronomy.Horizon(date, observer, equator.ra, equator.dec, "normal");
); const illumination = astronomy.Illumination(bodyMoon, date);
const N = normalizeDeg(125.1228 - 0.0529538083 * d); return {
const i = toRad(5.1454); altitude: (Number(horizon?.altitude) || 0) * Math.PI / 180,
const w = normalizeDeg(318.0634 + 0.1643573223 * d); illumination: Math.max(0, Math.min(1, Number(illumination?.phase_fraction) || 0)),
const a = 60.2666; };
const e = 0.0549; } catch (error) {
const M = normalizeDeg(115.3654 + 13.0649929509 * d); return { altitude: Number.NEGATIVE_INFINITY, illumination: 0 };
const E = M + (180 / Math.PI) * e * Math.sin(toRad(M)) * (1 + e * Math.cos(toRad(M))); }
const xv = a * (Math.cos(toRad(E)) - e);
const yv = a * (Math.sqrt(1 - e * e) * Math.sin(toRad(E)));
const v = Math.atan2(yv, xv);
const r = Math.sqrt(xv * xv + yv * yv);
const xh = r * (Math.cos(toRad(N)) * Math.cos(v + toRad(w)) - Math.sin(toRad(N)) * Math.sin(v + toRad(w)) * Math.cos(i));
const yh = r * (Math.sin(toRad(N)) * Math.cos(v + toRad(w)) + Math.cos(toRad(N)) * Math.sin(v + toRad(w)) * Math.cos(i));
const zh = r * Math.sin(v + toRad(w)) * Math.sin(i);
const moonLon = Math.atan2(yh, xh);
const moonLat = Math.atan2(zh, Math.sqrt(xh * xh + yh * yh));
const obliquity = toRad(23.4393 - 0.0000004 * d);
const xe = r * Math.cos(moonLon) * Math.cos(moonLat);
const ye = r * Math.sin(moonLon) * Math.cos(moonLat);
const ze = r * Math.sin(moonLat);
const xeq = xe;
const yeq = ye * Math.cos(obliquity) - ze * Math.sin(obliquity);
const zeq = ye * Math.sin(obliquity) + ze * Math.cos(obliquity);
const raHours = (((Math.atan2(yeq, xeq) * 180 / Math.PI) / 15) + 24) % 24;
const dec = Math.atan2(zeq, Math.sqrt(xeq * xeq + yeq * yeq));
const lstHours = (this._gmstHours(jd) + lonHours + 24) % 24;
const hourAngle = toRad((lstHours - raHours) * 15);
const altitude = Math.asin(
Math.sin(dec) * Math.sin(lat) + Math.cos(dec) * Math.cos(lat) * Math.cos(hourAngle),
);
const elongation = Math.acos(
Math.cos(moonLat) * Math.cos(moonLon - toRad(sunLongitude)),
);
const illumination = Math.max(0, Math.min(1, (1 - Math.cos(elongation)) / 2));
return { altitude, illumination };
} }
_moonTrackState() { _moonTrackState() {