feat(storm-intel): richer event data + NWS boundary label in drawer and map tooltip
useStormEvents: - Extract windSpeed (windtag), expireTime, warningDurationMins, phenomenaCode/Label - Calculate polygonAreaSqMi via shoelace formula; estimate homes at 1800/sq mi StormDetailDrawer — new sections: - Weather Metrics: hail size with damage descriptor, wind speed with severity label - Impact Zone: warning area sq miles + estimated homes in zone - Warning Timeline: issued time, expired time, duration, issuing NWS office - NWS Boundary note: explains the shape is a meteorologist-drawn warning zone Map tooltip: - Shows phenomenaLabel, wind speed, area sq mi - Footer section labeled "NWS Warning Boundary" with shape explanation
This commit is contained in:
@@ -60,6 +60,32 @@ function derivedAreaName(rawCoords, ph) {
|
||||
return `${city} — ${label}`;
|
||||
}
|
||||
|
||||
// Shoelace formula — polygon is Leaflet [lat,lon] pairs
|
||||
function polygonAreaSqMi(polygon) {
|
||||
const n = polygon.length;
|
||||
if (n < 3) return 0;
|
||||
const avgLat = polygon.reduce((s, p) => s + p[0], 0) / n;
|
||||
const mLat = 69.0;
|
||||
const mLon = 69.0 * Math.cos(avgLat * Math.PI / 180);
|
||||
let area = 0;
|
||||
for (let i = 0; i < n; i++) {
|
||||
const [lat1, lon1] = polygon[i];
|
||||
const [lat2, lon2] = polygon[(i + 1) % n];
|
||||
area += (lon1 * mLon) * (lat2 * mLat) - (lon2 * mLon) * (lat1 * mLat);
|
||||
}
|
||||
return Math.abs(area) / 2;
|
||||
}
|
||||
|
||||
const PH_LABEL = {
|
||||
SV: 'Severe Thunderstorm Warning',
|
||||
TO: 'Tornado Warning',
|
||||
FF: 'Flash Flood Warning',
|
||||
WS: 'Winter Storm Warning',
|
||||
BZ: 'Blizzard Warning',
|
||||
WW: 'Winter Weather Advisory',
|
||||
IS: 'Ice Storm Warning',
|
||||
};
|
||||
|
||||
function featureToEvent(feature, idx) {
|
||||
const p = feature.properties || {};
|
||||
const ph = p.phenomena || '';
|
||||
@@ -70,7 +96,18 @@ function featureToEvent(feature, idx) {
|
||||
|
||||
const polygon = rawCoords.map(([lon, lat]) => [lat, lon]); // → Leaflet [lat,lon]
|
||||
|
||||
const hailIn = p.hailtag ? parseFloat(p.hailtag) : null;
|
||||
const hailIn = p.hailtag ? parseFloat(p.hailtag) : null;
|
||||
const windMph = p.windtag ? parseInt(p.windtag, 10) : null;
|
||||
const issueTime = p.issue || p.polygon_begin || null;
|
||||
const expireTime = p.expire || p.polygon_end || null;
|
||||
|
||||
const warningDurationMins = (issueTime && expireTime)
|
||||
? Math.round((new Date(expireTime) - new Date(issueTime)) / 60000)
|
||||
: null;
|
||||
|
||||
const areaSqMi = Math.round(polygonAreaSqMi(polygon));
|
||||
// Suburban DFW density ~1 800 homes/sq mi — conservative (polygons often include open land)
|
||||
const estHomes = Math.round(areaSqMi * 1800);
|
||||
|
||||
const type = ph === 'TO' ? 'tornado'
|
||||
: ph === 'FF' ? 'flood'
|
||||
@@ -78,6 +115,7 @@ function featureToEvent(feature, idx) {
|
||||
: ph === 'WW' || ph === 'IS' ? 'ice'
|
||||
: hailIn !== null ? 'hail'
|
||||
: 'wind';
|
||||
|
||||
const severity = ph === 'TO' ? 'severe'
|
||||
: hailIn !== null
|
||||
? (hailIn >= 2.0 ? 'severe' : hailIn >= 1.5 ? 'significant' : hailIn >= 1.0 ? 'moderate' : 'trace')
|
||||
@@ -85,16 +123,23 @@ function featureToEvent(feature, idx) {
|
||||
: 'significant';
|
||||
|
||||
return {
|
||||
id: `IEM-${WFO}-${ph}-${p.year ?? ''}-${p.eventid ?? idx}`,
|
||||
date: p.issue || p.polygon_begin || new Date().toISOString(),
|
||||
id: `IEM-${WFO}-${ph}-${p.year ?? ''}-${p.eventid ?? idx}`,
|
||||
date: issueTime || new Date().toISOString(),
|
||||
expireTime,
|
||||
warningDurationMins,
|
||||
type,
|
||||
severity,
|
||||
maxHailSize: hailIn,
|
||||
areaName: derivedAreaName(rawCoords, ph),
|
||||
county: 'Collin',
|
||||
estimatedHomes: null,
|
||||
description: `${type === 'tornado' ? 'Tornado Warning' : type === 'flood' ? 'Flash Flood Warning' : 'Severe Thunderstorm Warning'} — NWS ${WFO}`,
|
||||
source: 'IEM/NWS',
|
||||
phenomenaCode: ph,
|
||||
phenomenaLabel: PH_LABEL[ph] ?? ph,
|
||||
maxHailSize: hailIn,
|
||||
windSpeed: windMph,
|
||||
areaName: derivedAreaName(rawCoords, ph),
|
||||
county: 'Collin',
|
||||
areaSqMi,
|
||||
estimatedHomes: estHomes > 0 ? estHomes : null,
|
||||
description: `${PH_LABEL[ph] ?? ph} — NWS ${WFO}`,
|
||||
source: 'IEM/NWS',
|
||||
wfo: WFO,
|
||||
polygon,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user