← back to Bertha
src/lib/weather.js
141 lines
const NWS_BASE = 'https://api.weather.gov';
const USER_AGENT = 'Bertha-Weather-Bot/1.0 (steve@designerwallcoverings.com)';
async function nwsFetch(url) {
const res = await fetch(url, {
headers: {
'User-Agent': USER_AGENT,
'Accept': 'application/geo+json'
}
});
if (!res.ok) throw new Error(`NWS API ${res.status}: ${res.statusText} for ${url}`);
return res.json();
}
// Get gridpoint mapping for a lat/lon
async function getGridPoint(lat, lon) {
return nwsFetch(`${NWS_BASE}/points/${lat},${lon}`);
}
// Get forecast for a gridpoint
async function getForecast(office, gridX, gridY) {
return nwsFetch(`${NWS_BASE}/gridpoints/${office}/${gridX},${gridY}/forecast`);
}
// Get hourly forecast for a gridpoint
async function getHourlyForecast(office, gridX, gridY) {
return nwsFetch(`${NWS_BASE}/gridpoints/${office}/${gridX},${gridY}/forecast/hourly`);
}
// Get raw gridpoint data (quantitative)
async function getGridData(office, gridX, gridY) {
return nwsFetch(`${NWS_BASE}/gridpoints/${office}/${gridX},${gridY}`);
}
// Get active alerts for a point
async function getAlerts(lat, lon) {
return nwsFetch(`${NWS_BASE}/alerts/active?point=${lat},${lon}`);
}
// Get station observations
async function getStationObs(stationId) {
return nwsFetch(`${NWS_BASE}/stations/${stationId}/observations/latest`);
}
// Get list of stations near a point
async function getNearbyStations(office, gridX, gridY) {
return nwsFetch(`${NWS_BASE}/gridpoints/${office}/${gridX},${gridY}/stations`);
}
// City coordinate lookup
const CITY_COORDS = {
'New York': { lat: 40.7128, lon: -74.0060 },
'Los Angeles': { lat: 34.0522, lon: -118.2437 },
'Chicago': { lat: 41.8781, lon: -87.6298 },
'Houston': { lat: 29.7604, lon: -95.3698 },
'Phoenix': { lat: 33.4484, lon: -112.0740 },
'Philadelphia': { lat: 39.9526, lon: -75.1652 },
'San Antonio': { lat: 29.4241, lon: -98.4936 },
'San Diego': { lat: 32.7157, lon: -117.1611 },
'Dallas': { lat: 32.7767, lon: -96.7970 },
'San Francisco': { lat: 37.7749, lon: -122.4194 },
'Seattle': { lat: 47.6062, lon: -122.3321 },
'Denver': { lat: 39.7392, lon: -104.9903 },
'Boston': { lat: 42.3601, lon: -71.0589 },
'Miami': { lat: 25.7617, lon: -80.1918 },
'Atlanta': { lat: 33.7490, lon: -84.3880 },
'Minneapolis': { lat: 44.9778, lon: -93.2650 },
'Detroit': { lat: 42.3314, lon: -83.0458 },
'Portland': { lat: 45.5051, lon: -122.6750 },
'Las Vegas': { lat: 36.1699, lon: -115.1398 },
'Nashville': { lat: 36.1627, lon: -86.7816 },
'Washington': { lat: 38.9072, lon: -77.0369 },
'Cleveland': { lat: 41.4993, lon: -81.6944 },
'Pittsburgh': { lat: 40.4406, lon: -79.9959 },
'St. Louis': { lat: 38.6270, lon: -90.1994 },
'Kansas City': { lat: 39.0997, lon: -94.5786 },
'Tampa': { lat: 27.9506, lon: -82.4572 },
'New Orleans': { lat: 29.9511, lon: -90.0715 },
'Oklahoma City': { lat: 35.4676, lon: -97.5164 },
};
function getCityCoords(cityName) {
return CITY_COORDS[cityName] || null;
}
// Generate geom_hash for consistent location keys
function geomHash(lat, lon) {
return `${Math.round(lat * 100)}_${Math.round(lon * 100)}`;
}
// Extract forecast features for a specific variable and time
function extractForecastFeatures(forecastData, variable, targetTime) {
const periods = forecastData?.properties?.periods || [];
if (!periods.length) return null;
const features = {
variable,
forecast_count: periods.length,
values: [],
timestamps: []
};
for (const period of periods) {
let value = null;
if (variable === 'temperature') {
value = period.temperature;
features.unit = period.temperatureUnit;
} else if (variable === 'wind') {
const match = period.windSpeed?.match(/(\d+)/);
if (match) value = parseInt(match[1]);
features.unit = 'mph';
} else if (variable === 'precipitation') {
const prob = period.probabilityOfPrecipitation?.value;
value = prob != null ? prob : 0;
features.unit = 'percent';
}
if (value !== null) {
features.values.push(value);
features.timestamps.push(period.startTime);
}
}
if (features.values.length > 0) {
features.mean = features.values.reduce((a, b) => a + b, 0) / features.values.length;
features.min = Math.min(...features.values);
features.max = Math.max(...features.values);
features.range = features.max - features.min;
features.latest = features.values[0];
}
return features;
}
module.exports = {
getGridPoint, getForecast, getHourlyForecast, getGridData,
getAlerts, getStationObs, getNearbyStations,
getCityCoords, geomHash, extractForecastFeatures,
CITY_COORDS, NWS_BASE
};