Add NHC 7-day tropical outlook for early hurricane warning
This commit is contained in:
parent
8d8bbbc476
commit
4b9483a835
|
|
@ -370,6 +370,9 @@ async function updateWeather() {
|
|||
if (d.alerts && d.alerts.length) {
|
||||
html += ' <span class="wx-alert">⚠️ ' + d.alerts.join(', ') + '</span>';
|
||||
}
|
||||
if (d.tropical) {
|
||||
html += ' <span class="wx-alert">🌀 ' + d.tropical + '</span>';
|
||||
}
|
||||
document.getElementById('weather-line').innerHTML = html;
|
||||
} catch(e) {}
|
||||
}
|
||||
|
|
|
|||
76
server.js
76
server.js
|
|
@ -123,24 +123,68 @@ app.get('/api/weather', async (req, res) => {
|
|||
result.high = parseInt(today.maxtempF);
|
||||
result.low = parseInt(today.mintempF);
|
||||
|
||||
// NWS severe alerts
|
||||
const nws = await new Promise((resolve, reject) => {
|
||||
const r = require('https').get('https://api.weather.gov/alerts/active?point=27.7676,-82.6403', {
|
||||
headers: { 'User-Agent': 'james-dashboard' }
|
||||
}, (resp) => {
|
||||
let body = '';
|
||||
resp.on('data', c => body += c);
|
||||
resp.on('end', () => { try { resolve(JSON.parse(body)); } catch(e) { reject(e); } });
|
||||
// NWS severe alerts (imminent)
|
||||
try {
|
||||
const nws = await new Promise((resolve, reject) => {
|
||||
const r = require('https').get('https://api.weather.gov/alerts/active?point=27.7676,-82.6403', {
|
||||
headers: { 'User-Agent': 'james-dashboard' }
|
||||
}, (resp) => {
|
||||
let body = '';
|
||||
resp.on('data', c => body += c);
|
||||
resp.on('end', () => { try { resolve(JSON.parse(body)); } catch(e) { reject(e); } });
|
||||
});
|
||||
r.on('error', reject);
|
||||
r.setTimeout(10000, () => { r.destroy(); reject(new Error('timeout')); });
|
||||
});
|
||||
r.on('error', reject);
|
||||
r.setTimeout(10000, () => { r.destroy(); reject(new Error('timeout')); });
|
||||
});
|
||||
for (const f of (nws.features || [])) {
|
||||
const event = (f.properties.event || '').toLowerCase();
|
||||
if (SEVERE_EVENTS.some(s => event.includes(s))) {
|
||||
result.alerts.push(f.properties.event);
|
||||
for (const f of (nws.features || [])) {
|
||||
const event = (f.properties.event || '').toLowerCase();
|
||||
if (SEVERE_EVENTS.some(s => event.includes(s))) {
|
||||
result.alerts.push(f.properties.event);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch(e) {}
|
||||
|
||||
// NHC Tropical Weather Outlook (7-day formation, hurricane season)
|
||||
try {
|
||||
const twoHtml = await new Promise((resolve, reject) => {
|
||||
const r = require('https').get('https://www.nhc.noaa.gov/text/MIATWOAT.shtml', {
|
||||
headers: { 'User-Agent': 'james-dashboard' }
|
||||
}, (resp) => {
|
||||
let body = '';
|
||||
resp.on('data', c => body += c);
|
||||
resp.on('end', () => resolve(body));
|
||||
});
|
||||
r.on('error', reject);
|
||||
r.setTimeout(10000, () => { r.destroy(); reject(new Error('timeout')); });
|
||||
});
|
||||
const preMatch = twoHtml.match(/<pre>([\s\S]*?)<\/pre>/);
|
||||
if (preMatch) {
|
||||
const text = preMatch[1].replace(/<[^>]+>/g, '');
|
||||
// Skip if off-season / "not expected"
|
||||
if (!text.includes('formation is not expected')) {
|
||||
// Extract formation probabilities
|
||||
const lines = text.split('\n');
|
||||
const formations = [];
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const pctMatch = lines[i].match(/(\d+)\s*percent/i);
|
||||
if (pctMatch && parseInt(pctMatch[1]) >= 20) {
|
||||
// Find nearby context about Gulf/Caribbean
|
||||
const context = lines.slice(Math.max(0, i-3), i+2).join(' ');
|
||||
const loc = context.match(/Gulf|Caribbean|Atlantic|Florida|Bahamas/i);
|
||||
formations.push({
|
||||
pct: parseInt(pctMatch[1]),
|
||||
location: loc ? loc[0] : 'Atlantic',
|
||||
text: lines[i].trim()
|
||||
});
|
||||
}
|
||||
}
|
||||
if (formations.length) {
|
||||
const worst = formations.sort((a,b) => b.pct - a.pct)[0];
|
||||
result.tropical = `${worst.pct}% tropical development (${worst.location}, 7-day)`;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch(e) {}
|
||||
|
||||
weatherCache = { data: result, ts: Date.now() };
|
||||
res.json(result);
|
||||
|
|
|
|||
Loading…
Reference in New Issue