From 97cf840847e869d01eb4c202dfb646f70c3ca23d Mon Sep 17 00:00:00 2001 From: ztimson Date: Wed, 24 Jun 2026 10:12:58 -0400 Subject: [PATCH] + UV index label * AQ fixes + Sun/moon distance + moon position * moon illumination --- client/src/components/CurrentWeather.vue | 2 +- sensors/main.py | 30 ++++++++---- server/celestial.mjs | 61 ++++++++++++++++++++++-- 3 files changed, 81 insertions(+), 12 deletions(-) diff --git a/client/src/components/CurrentWeather.vue b/client/src/components/CurrentWeather.vue index 3efb39a..7aae071 100644 --- a/client/src/components/CurrentWeather.vue +++ b/client/src/components/CurrentWeather.vue @@ -147,7 +147,7 @@ onMounted(async () => {
Wind{{ data.wind_gusts || 0 }} Kph ({{ dir(data.wind_direction || 0) }})
Clouds{{ (data.clouds || 0).toFixed(0) }}%
Air Quality{{ data.air_quality }} ({{data.air_quality_label}})
-
UV index{{ data.uv_index }}
+
UV index{{ (data.uv_index || 0).toFixed(1) }}
diff --git a/sensors/main.py b/sensors/main.py index d84ab71..35cf59c 100644 --- a/sensors/main.py +++ b/sensors/main.py @@ -132,13 +132,17 @@ def vpd(tc, rh): def gas_to_aqi(gas_ohms, humidity, gas_reference): if gas_ohms is None: return None, None - score = min(max(round(min(gas_ohms/gas_reference,1.0)*75 + (25 - abs(humidity-40)*0.5)), 0), 100) - label = 'Very Poor' - if score >= 80: label = 'Excellent' - elif score >= 60: label = 'Good' - elif score >= 40: label = 'Fair' - elif score >= 20: label = 'Poor' - return score, label + gas_score = min(gas_ohms / gas_reference, 1.0) * 75 + hum_score = 25 - abs(humidity - 40) * 0.5 + quality = min(max(gas_score + hum_score, 0), 100) + aqi = round((1 - quality / 100) * 500) + if aqi <= 50: label = 'Good' + elif aqi <= 100: label = 'Standard' + elif aqi <= 150: label = 'Warning' + elif aqi <= 200: label = 'Unhealthy' + elif aqi <= 300: label = 'Very Unhealthy' + else: label = 'Hazardous' + return aqi, label def sea_level_pressure(pressure_hpa, alt_m): if alt_m is None: return None @@ -362,6 +366,13 @@ def visibility_estimate(lux, humidity, solar_el=None): lux_factor = 1.0 return round(base_km * hum_factor * lux_factor, 1) +def uv_index_label(uvi): + if uvi < 3: return 'Low' + elif uvi < 6: return 'Moderate' + elif uvi < 8: return 'High' + elif uvi < 11: return 'Very High' + else: return 'Extreme' + def read_ltr(c, sensor, solar_el, humidity=None): global uv_dose_mj, uv_dose_date, last_uv_time global dli_lux_acc, dli_date, last_lux_time, daylight_start @@ -414,7 +425,10 @@ def read_ltr(c, sensor, solar_el, humidity=None): 'daily_light_integral': dli, 'daylight': daylight_hours, 'visibility': visibility_estimate(lux, humidity, solar_el) if humidity else None, - }, tags={'clouds_label': cloud_label or ''}) + }, tags={ + 'clouds_label': cloud_label or '' + 'uv_index_label': uv_index_label(uvi), + }) # ── TF-Luna ─────────────────────────────────────────────────────────────────── diff --git a/server/celestial.mjs b/server/celestial.mjs index ca00a8e..35a765f 100644 --- a/server/celestial.mjs +++ b/server/celestial.mjs @@ -82,12 +82,66 @@ export function sunActivityLabel(f107) { return 'Very High'; } +function moonPosition(lat, lon, date = new Date()) { + const d = jdn(date) - 2451545; + const L = (218.316 + 13.176396 * d) % 360; + const M = (134.963 + 13.064993 * d) % 360; + const F = (93.272 + 13.229350 * d) % 360; + const lon_ = L + 6.289 * Math.sin(M * RAD); + const b = 5.128 * Math.sin(F * RAD); + const dec = Math.asin( + Math.sin(b * RAD) * Math.cos(23.4397 * RAD) + + Math.cos(b * RAD) * Math.sin(23.4397 * RAD) * Math.sin(lon_ * RAD) + ) * DEG; + const ra = Math.atan2( + Math.sin(lon_ * RAD) * Math.cos(23.4397 * RAD) - Math.tan(b * RAD) * Math.sin(23.4397 * RAD), + Math.cos(lon_ * RAD) + ) * DEG; + const UT = date.getUTCHours() + date.getUTCMinutes() / 60 + date.getUTCSeconds() / 3600; + const GMST = (6.697375 + 0.0657098242 * d + UT) % 24; + const LMST = (GMST + lon / 15) % 24; + const ha = LMST * 15 - ra; + const elev = Math.asin( + Math.sin(lat * RAD) * Math.sin(dec * RAD) + + Math.cos(lat * RAD) * Math.cos(dec * RAD) * Math.cos(ha * RAD) + ) * DEG; + const az = Math.atan2( + -Math.sin(ha * RAD), + Math.tan(dec * RAD) * Math.cos(lat * RAD) - Math.sin(lat * RAD) * Math.cos(ha * RAD) + ) * DEG; + return { + moon_elevation: Math.round(elev * 10) / 10, + moon_azimuth: Math.round(((az + 360) % 360) * 10) / 10, + }; +} + +function sunMoonDistance(date = new Date()) { + const d = jdn(date) - 2451545; + // Sun ecliptic longitude + const Ls = (280.46 + 0.9856474 * d) % 360; + const gs = (357.528 + 0.9856003 * d) % 360; + const sunLon = Ls + 1.915 * Math.sin(gs * RAD) + 0.02 * Math.sin(2 * gs * RAD); + // Moon ecliptic longitude + const Lm = (218.316 + 13.176396 * d) % 360; + const Mm = (134.963 + 13.064993 * d) % 360; + const Fm = (93.272 + 13.229350 * d) % 360; + const moonLon = Lm + 6.289 * Math.sin(Mm * RAD); + const moonLat = 5.128 * Math.sin(Fm * RAD); + // Angular separation + const dLon = (moonLon - sunLon) * RAD; + const sep = Math.acos( + Math.cos(moonLat * RAD) * Math.cos(dLon) + ) * DEG; + return { sun_moon_separation: Math.round(sep * 10) / 10 }; +} + function moonPhase(date = new Date()) { const jd = jdn(date); const cycle = 29.53058867; const known = 2451550.1; const phase = ((jd - known) % cycle + cycle) % cycle; - const illum = Math.round((1 - Math.cos(phase / cycle * 2 * Math.PI)) / 2 * 100); + // More accurate illumination using proper phase angle + const illum = Math.round((1 - Math.cos(phase / cycle * 2 * Math.PI)) / 2 * 1000) / 10; let name; if(phase < 1.85) name = 'New Moon'; else if(phase < 7.38) name = 'Waxing Crescent'; @@ -97,7 +151,7 @@ function moonPhase(date = new Date()) { else if(phase < 22.15) name = 'Waning Gibbous'; else if(phase < 23.99) name = 'Last Quarter'; else name = 'Waning Crescent'; - return {moon_phase: name, moon_illumination: illum}; + return { moon_phase: name, moon_illumination: illum }; } function nextMoonEvents(date = new Date()) { @@ -171,9 +225,10 @@ export function getCelestialCurrent(lat, lon, date = new Date()) { ...sunPosition(lat, lon, date), ...sunriseSunset(lat, lon, date), ...moonPhase(date), + ...moonPosition(lat, lon, date), + ...sunMoonDistance(date), ...nextMoonEvents(date), ...moonriseMoonset(lat, lon, date), - ...nextSolsticeEquinox(date), }; }