+ UV index label
* AQ fixes + Sun/moon distance + moon position * moon illumination
This commit is contained in:
@@ -147,7 +147,7 @@ onMounted(async () => {
|
||||
<div class="stat"><span class="stat-label">Wind</span><span class="stat-value">{{ data.wind_gusts || 0 }} Kph <span class="fg-muted">({{ dir(data.wind_direction || 0) }})</span></span></div>
|
||||
<div class="stat"><span class="stat-label">Clouds</span><span class="stat-value">{{ (data.clouds || 0).toFixed(0) }}%</span></div>
|
||||
<div class="stat"><span class="stat-label">Air Quality</span><span class="stat-value">{{ data.air_quality }} <span class="fg-muted">({{data.air_quality_label}})</span></span></div>
|
||||
<div class="stat"><span class="stat-label">UV index</span><span class="stat-value">{{ data.uv_index }}</span></div>
|
||||
<div class="stat"><span class="stat-label">UV index</span><span class="stat-value">{{ (data.uv_index || 0).toFixed(1) }}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -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 ───────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@@ -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),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user