Auf ae.js umgestellt
This commit is contained in:
@@ -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
@@ -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() {
|
||||||
|
|||||||
Reference in New Issue
Block a user