Files
weather-station/server/space.mjs
2026-06-21 22:14:04 -04:00

70 lines
2.8 KiB
JavaScript

import { ssnFromFlux, sunActivityLabel } from './celestial.mjs'
const CACHE = {}
const CACHE_MS = 10 * 60 * 1000
async function cachedFetch(key, url) {
const now = Date.now()
if (CACHE[key] && now - CACHE[key].ts < CACHE_MS) return CACHE[key].data
const res = await fetch(url)
const data = await res.json()
CACHE[key] = { ts: now, data }
return data
}
async function getKpAp() {
const data = await cachedFetch('kp', 'https://services.swpc.noaa.gov/products/noaa-planetary-k-index.json')
// First row is always the header
const rows = Array.isArray(data) ? data.slice(1).filter(r => Array.isArray(r)) : []
if (!rows.length) return {}
const latest = rows[rows.length - 1]
const kp = parseFloat(latest[1])
const ap = parseFloat(latest[2])
return {
space_kp_index: isNaN(kp) ? null : kp,
space_ap_index: isNaN(ap) ? null : ap,
space_geomagnetic_storm: kp >= 7 ? 'Severe' : kp >= 6 ? 'Strong' : kp >= 5 ? 'Moderate' : kp >= 4 ? 'Minor' : 'None',
}
}
async function getSolarWind() {
const [plasma, mag] = await Promise.all([
cachedFetch('plasma', 'https://services.swpc.noaa.gov/products/solar-wind/plasma.json'),
cachedFetch('mag', 'https://services.swpc.noaa.gov/products/solar-wind/mag.json'),
])
// Skip header row, find last valid row
const pRows = Array.isArray(plasma) ? plasma.slice(1).filter(r => Array.isArray(r) && r[2] !== null) : []
const mRows = Array.isArray(mag) ? mag.slice(1).filter(r => Array.isArray(r) && r[3] !== null) : []
const p = pRows[pRows.length - 1] || []
const m = mRows[mRows.length - 1] || []
return {
space_solar_wind_speed_kms: p[2] != null ? parseFloat(p[2]) : null,
space_solar_wind_density: p[1] != null ? parseFloat(p[1]) : null,
space_solar_wind_temp: p[3] != null ? parseFloat(p[3]) : null,
space_imf_bz: m[3] != null ? parseFloat(m[3]) : null,
space_imf_bt: m[6] != null ? parseFloat(m[6]) : null,
}
}
async function getSolarFlux() {
const data = await cachedFetch('flux', 'https://services.swpc.noaa.gov/products/10cm-flux-30-day.json')
// Format: [["yyyy-mm-dd", flux, ...], ...] — no header row on this one
const rows = Array.isArray(data) ? data.filter(r => Array.isArray(r) && !isNaN(parseFloat(r[1]))) : []
if (!rows.length) return {}
const f107 = parseFloat(rows[rows.length - 1][1])
return {
space_solar_flux_f107: isNaN(f107) ? null : f107,
sun_ssn: ssnFromFlux(f107),
sun_activity_label: sunActivityLabel(f107),
}
}
export async function getSpaceWeather() {
const [kpAp, wind, flux] = await Promise.allSettled([getKpAp(), getSolarWind(), getSolarFlux()])
return {
...(kpAp.status === 'fulfilled' ? kpAp.value : {}),
...(wind.status === 'fulfilled' ? wind.value : {}),
...(flux.status === 'fulfilled' ? flux.value : {}),
}
}