OpenMeteo fixes

This commit is contained in:
2026-06-27 17:52:32 -04:00
parent 835856e74c
commit 96a86c4d12
2 changed files with 144 additions and 61 deletions

View File

@@ -2,6 +2,8 @@ import { existsSync, mkdirSync } from 'fs'
import { resolve, dirname } from 'path'
import { fileURLToPath } from 'url'
import { tzOffsetMinutes } from './config.mjs'
import { getWeatherCondition } from './forecast.mjs'
import { getCelestialForecast } from './celestial.mjs'
const DIR = dirname(fileURLToPath(import.meta.url))
const ICON_DIR = resolve(DIR, 'public', 'icons')
@@ -87,43 +89,78 @@ export async function dailyWeather(lat, lon, start, end) {
const data = await cachedFetch(`daily_${lat}_${lon}_${startStr}_${endStr}`, url)
if (!data?.daily?.time) return []
return Promise.all(data.daily.time.map(async (time, i) => {
const rows = data.daily.time.map((time, i) => {
const code = data.daily.weathercode[i]
const icon = OWM_ICONS[code] || '01d'
const temp = data.daily.temperature_2m_max[i]
const tempMax = data.daily.temperature_2m_max[i]
const tempMin = data.daily.temperature_2m_min[i]
const temp = tempMax
const humidity = data.daily.relative_humidity_2m_mean[i]
const uv = data.daily.uv_index_max[i]
const precip = data.daily.precipitation_sum[i] ?? 0
const precipChance = data.daily.precipitation_probability_max[i] ?? 0
const windSpeed = data.daily.windspeed_10m_max[i] ?? 0
const windGusts = data.daily.windgusts_10m_max[i] ?? 0
// "2026-03-01" → local midnight by shifting from UTC midnight by tz offset
const utcMidnight = new Date(`${time}T00:00:00Z`)
const offset = tzOffsetMinutes(utcMidnight)
const utcMidnight = new Date(`${time}T00:00:00Z`)
const offset = tzOffsetMinutes(utcMidnight)
const localMidnight = new Date(utcMidnight.getTime() - offset * 60_000)
return {
const sunrise = data.daily.sunrise[i] ? new Date(data.daily.sunrise[i]) : null
const sunset = data.daily.sunset[i] ? new Date(data.daily.sunset[i]) : null
const daylight = sunrise && sunset
? Math.round((sunset - sunrise) / 3600_000 * 100) / 100
: null
const clouds = humidity / 100 * 0.8 // rough estimate — not provided by daily endpoint
const row = {
time: localMidnight,
daytime: true,
label: WMO[code] || 'Unknown',
icon,
code,
time: localMidnight,
temperature: temp,
temperature_max: temp,
temperature_min: data.daily.temperature_2m_min[i],
temperature_max: tempMax,
temperature_min: tempMin,
humidity,
humidity_abs: absoluteHumidity(temp, humidity),
dew_point: dewPoint(temp, humidity),
heat_index: heatIndex(temp, humidity),
vapor_pressure_deficit: vaporPressureDeficit(temp, humidity),
precipitation: data.daily.precipitation_sum[i],
precipitation_chance: data.daily.precipitation_probability_max[i],
wind_speed: data.daily.windspeed_10m_max[i],
wind_gusts: data.daily.windgusts_10m_max[i],
wind_direction: data.daily.winddirection_10m_dominant[i],
uv_index: uv,
solar_wm2: data.daily.shortwave_radiation_sum[i],
clouds: null,
sunrise: data.daily.sunrise[i] ? new Date(data.daily.sunrise[i]) : null,
sunset: data.daily.sunset[i] ? new Date(data.daily.sunset[i]) : null,
pressure_hpa: null,
pressure_slp: null,
pressure_rate: null,
storm_trend: 0,
wind_speed: windSpeed,
wind_gusts: windGusts,
wind_direction: data.daily.winddirection_10m_dominant[i] ?? 0,
clouds,
raining: precipChance > 60 ? 'True' : 'False',
precipitation: precip,
precipitation_chance: precipChance,
accumulation: precip,
uv_index: data.daily.uv_index_max[i] ?? 0,
uv_index_max: data.daily.uv_index_max[i] ?? 0,
uv_dose: 0,
solar_wm2: data.daily.shortwave_radiation_sum[i] ?? 0,
daily_light_integral: data.daily.shortwave_radiation_sum[i]
? Math.round(data.daily.shortwave_radiation_sum[i] * 3600 / 1_000_000 * 4.57 * 100) / 100
: 0,
lux: 0,
visibility: 50,
sunrise,
sunset,
daylight,
lightning_rate: 0,
lightning_distance: null,
seismic_magnitude: null,
air_quality: null,
ground_distance: null,
}
}))
return { ...row, ...getWeatherCondition(row) }
})
return rows
}
// ── Hourly ────────────────────────────────────────────────────────────────────
@@ -146,8 +183,6 @@ export async function hourlyWeather(lat, lon, start, end) {
const data = await cachedFetch(`hourly_${lat}_${lon}_${startStr}_${endStr}`, url)
if (!data?.hourly?.time) return []
// Open-Meteo returns local time strings e.g. "2026-03-01T06:00" (no Z)
// sunMap keyed by date portion of those local strings
const sunMap = {}
if (data.daily?.time) {
data.daily.time.forEach((date, i) => {
@@ -158,44 +193,79 @@ export async function hourlyWeather(lat, lon, start, end) {
})
}
return Promise.all(data.hourly.time.map(async (time, i) => {
const rows = data.hourly.time.map((time, i) => {
const dateKey = time.slice(0, 10)
const sun = sunMap[dateKey] ?? {}
const t = new Date(time + 'Z').getTime()
const daytime = sun.sunrise && sun.sunset
? t >= new Date(sun.sunrise.toISOString().replace('Z', '') + 'Z').getTime() && t < new Date(sun.sunset.toISOString().replace('Z', '') + 'Z').getTime()
? t >= sun.sunrise.getTime() && t < sun.sunset.getTime()
: false
const code = data.hourly.weathercode[i]
const icon = await ensureIcon(code).catch(() => null)
const temp = data.hourly.temperature_2m[i]
const humidity = data.hourly.relative_humidity_2m[i]
const uv = data.hourly.uv_index[i]
const clouds = Math.round(data.hourly.cloudcover[i]) / 100
const precip = data.hourly.precipitation[i] ?? 0
const precipChance = data.hourly.precipitation_probability[i] ?? 0
const windSpeed = data.hourly.windspeed_10m[i] ?? 0
const uv = data.hourly.uv_index[i] ?? 0
return {
const sunrise = sun.sunrise ?? null
const sunset = sun.sunset ?? null
const daylight = sunrise && sunset
? Math.round((sunset - sunrise) / 3600_000 * 100) / 100
: null
const row = {
time: new Date(time + 'Z'),
daytime,
label: WMO[code] || 'Unknown',
icon,
code,
icon: OWM_ICONS[code] || '01d',
temperature: temp,
temperature_max: temp,
temperature_min: temp,
humidity,
humidity_abs: absoluteHumidity(temp, humidity),
dew_point: dewPoint(temp, humidity),
dew_point: data.hourly.dewpoint_2m[i] ?? dewPoint(temp, humidity),
heat_index: heatIndex(temp, humidity),
vapor_pressure_deficit: data.hourly.vapour_pressure_deficit[i] ?? vaporPressureDeficit(temp, humidity),
pressure_hpa: data.hourly.surface_pressure[i],
precipitation: data.hourly.precipitation[i],
precipitation_chance: data.hourly.precipitation_probability[i],
wind_speed: data.hourly.windspeed_10m[i],
wind_gusts: data.hourly.windgusts_10m[i],
wind_direction: data.hourly.winddirection_10m[i],
pressure_hpa: data.hourly.surface_pressure[i] ?? null,
pressure_slp: null,
pressure_rate: null,
storm_trend: 0,
wind_speed: windSpeed,
wind_gusts: data.hourly.windgusts_10m[i] ?? 0,
wind_direction: data.hourly.winddirection_10m[i] ?? 0,
clouds,
raining: precipChance > 60 ? 'True' : 'False',
precipitation: precip,
precipitation_chance: precipChance,
accumulation: precip,
uv_index: uv,
solar_wm2: data.hourly.shortwave_radiation[i],
uv_index_max: uv,
uv_dose: 0,
solar_wm2: data.hourly.shortwave_radiation[i] ?? 0,
daily_light_integral: data.hourly.shortwave_radiation[i]
? Math.round(data.hourly.shortwave_radiation[i] * 3600 / 1_000_000 * 4.57 * 100) / 100
: 0,
lux: data.hourly.shortwave_radiation[i]
? Math.round(data.hourly.shortwave_radiation[i] * 120)
: 0,
visibility: data.hourly.visibility[i] != null
? Math.round(data.hourly.visibility[i] / 1000 * 10) / 10
: null,
: 50,
sunrise,
sunset,
daylight,
lightning_rate: 0,
lightning_distance: null,
seismic_magnitude: null,
air_quality: null,
ground_distance: null,
}
}))
return { ...row, ...getWeatherCondition(row) }
})
return rows
}