feat: Implement METAR runway visual range (RVR) decoding

Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
This commit is contained in:
2026-02-14 17:05:48 -07:00
parent a85ed5c11d
commit 4551aef2de
+38
View File
@@ -310,6 +310,42 @@
return `${code}: Altimeter ${alt.slice(0, 2)}.${alt.slice(2)} inches of mercury`; return `${code}: Altimeter ${alt.slice(0, 2)}.${alt.slice(2)} inches of mercury`;
} }
function decodeRunwayInfo(code) {
const parts = code.split('/');
const runway = `Runway ${parts[0].substring(1)}`;
const visPart = parts[1];
let visText;
function decodeVis(visStr) {
if (visStr.startsWith('P')) return `greater than ${visStr.substring(1)}`;
if (visStr.startsWith('M')) return `less than ${visStr.substring(1)}`;
return visStr;
}
if (visPart.includes('V')) {
const [minVis, maxVis] = visPart.replace('FT', '').split('V');
visText = `visual range variable between ${decodeVis(minVis)} and ${decodeVis(maxVis)} feet`;
} else {
const vis = visPart.replace('FT', '');
visText = `visual range ${decodeVis(vis)} feet`;
}
let trendText = '';
if (parts.length > 2 && parts[2]) {
const trendCode = parts[2];
const trendMap = {
'N': 'not significant',
'U': 'upward',
'D': 'downward'
};
const trendDesc = trendMap[trendCode] || 'unknown';
trendText = `, trend ${trendDesc} (${trendCode})`;
}
return `${code}: ${runway} ${visText}${trendText}`;
}
function decodeSlp(code) { function decodeSlp(code) {
const pressure = parseInt(code.substring(3)); const pressure = parseInt(code.substring(3));
const hpa = (pressure < 500 ? 1000 : 900) + (pressure / 10); const hpa = (pressure < 500 ? 1000 : 900) + (pressure / 10);
@@ -701,6 +737,8 @@
sections.push({ raw: part, decoded: decodeTempDew(part) }); sections.push({ raw: part, decoded: decodeTempDew(part) });
} else if (part.startsWith('A') && part.length === 5 && !isNaN(part.substring(1))) { } else if (part.startsWith('A') && part.length === 5 && !isNaN(part.substring(1))) {
sections.push({ raw: part, decoded: decodeAltimeter(part) }); sections.push({ raw: part, decoded: decodeAltimeter(part) });
} else if (part.match(/^R\d{2}[LRC]?\//)) {
sections.push({ raw: part, decoded: decodeRunwayInfo(part) });
} else { } else {
console.log(`Unknown main METAR part: ${part} in METAR: ${metarString}`); console.log(`Unknown main METAR part: ${part} in METAR: ${metarString}`);
sections.push({ raw: part, decoded: `${part}: Unknown METAR component` }); sections.push({ raw: part, decoded: `${part}: Unknown METAR component` });