feat(storm-intel): real storm polygon data via Iowa Environmental Mesonet IEM SBW archive

api/storm-polygons.js — Vercel proxy that fetches historical NWS storm-based
warnings (severe thunderstorm + tornado) from IEM for WFO FWD (Fort Worth),
covering Collin County / Plano TX. Filters to local bounding box, derives
severity from polygon area (area2163 km²), estimates impacted homes from
density, derives area name from centroid. Cached 12h on Vercel edge.

useStormEvents.js — switched to real IEM data (USE_MOCK=false). Fetches full
36-month dataset once on mount; all range/type/severity filtering stays
client-side in useMemo for instant UI response. Falls back to
MOCK_STORM_EVENTS when /api routes are unavailable (local vite dev).
This commit is contained in:
Satyam-Rastogi
2026-05-18 23:45:50 +05:30
parent 7799a83a4a
commit fd353ab732
2 changed files with 161 additions and 6 deletions
+16 -6
View File
@@ -8,7 +8,8 @@
import { useState, useEffect, useMemo } from 'react';
import { MOCK_STORM_EVENTS } from '../data/mockStore';
const USE_MOCK = true;
// false = use real IEM/NWS data via /api/storm-polygons (falls back to mock if unreachable)
const USE_MOCK = false;
const DATE_RANGE_DAYS = { '1m': 30, '3m': 90, '6m': 180, '12m': 365, '18m': 548, '24m': 730, '36m': 1095 };
@@ -38,10 +39,17 @@ export function useStormEvents({
await new Promise(r => setTimeout(r, 450));
data = MOCK_STORM_EVENTS;
} else {
const days = DATE_RANGE_DAYS[dateRange] ?? 365;
const res = await fetch(`/api/storm-events?days=${days}&county=Collin`);
if (!res.ok) throw new Error(`Storm events error: ${res.status}`);
data = await res.json();
try {
// Always fetch full 36-month window so range changes are instant client-side.
// Falls back to MOCK_STORM_EVENTS when /api routes aren't available (local vite dev).
const res = await fetch('/api/storm-polygons?months=36');
if (!res.ok) throw new Error(`${res.status}`);
const json = await res.json();
if (!Array.isArray(json) || json.length === 0) throw new Error('empty');
data = json;
} catch {
data = MOCK_STORM_EVENTS;
}
}
if (!cancelled) setAllEvents(Array.isArray(data) ? data : []);
} catch (e) {
@@ -53,7 +61,9 @@ export function useStormEvents({
load();
return () => { cancelled = true; };
}, [dateRange]);
// Run once — 36mo dataset is fetched in full; all filtering is client-side in useMemo below
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const events = useMemo(() => {
let filtered = allEvents.filter(e => {