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) {
|
if (d.alerts && d.alerts.length) {
|
||||||
html += ' <span class="wx-alert">⚠️ ' + d.alerts.join(', ') + '</span>';
|
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;
|
document.getElementById('weather-line').innerHTML = html;
|
||||||
} catch(e) {}
|
} 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.high = parseInt(today.maxtempF);
|
||||||
result.low = parseInt(today.mintempF);
|
result.low = parseInt(today.mintempF);
|
||||||
|
|
||||||
// NWS severe alerts
|
// NWS severe alerts (imminent)
|
||||||
const nws = await new Promise((resolve, reject) => {
|
try {
|
||||||
const r = require('https').get('https://api.weather.gov/alerts/active?point=27.7676,-82.6403', {
|
const nws = await new Promise((resolve, reject) => {
|
||||||
headers: { 'User-Agent': 'james-dashboard' }
|
const r = require('https').get('https://api.weather.gov/alerts/active?point=27.7676,-82.6403', {
|
||||||
}, (resp) => {
|
headers: { 'User-Agent': 'james-dashboard' }
|
||||||
let body = '';
|
}, (resp) => {
|
||||||
resp.on('data', c => body += c);
|
let body = '';
|
||||||
resp.on('end', () => { try { resolve(JSON.parse(body)); } catch(e) { reject(e); } });
|
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);
|
for (const f of (nws.features || [])) {
|
||||||
r.setTimeout(10000, () => { r.destroy(); reject(new Error('timeout')); });
|
const event = (f.properties.event || '').toLowerCase();
|
||||||
});
|
if (SEVERE_EVENTS.some(s => event.includes(s))) {
|
||||||
for (const f of (nws.features || [])) {
|
result.alerts.push(f.properties.event);
|
||||||
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() };
|
weatherCache = { data: result, ts: Date.now() };
|
||||||
res.json(result);
|
res.json(result);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue