fix: Improve METAR remark decoding for directional and cloud-related remarks

Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
This commit is contained in:
2026-02-14 16:00:54 -07:00
parent 928e83f56f
commit 04793413ef
+52 -2
View File
@@ -260,7 +260,7 @@
'CB': 'Cumulonimbus' 'CB': 'Cumulonimbus'
}; };
const specialRemarks = { const specialRemarks = {
'FROIN': 'Frost on indicator', 'FROIN': 'Frontal passage in vicinity',
'CONTRAILS': 'Contrails observed', 'CONTRAILS': 'Contrails observed',
'VIRGA': 'Virga (precipitation not reaching the ground)', 'VIRGA': 'Virga (precipitation not reaching the ground)',
'HALO': 'Halo phenomenon observed' 'HALO': 'Halo phenomenon observed'
@@ -282,6 +282,11 @@
const location = parts[i+1]; const location = parts[i+1];
decoded.push(` - VIA ${location}: relayed via ${location}`); decoded.push(` - VIA ${location}: relayed via ${location}`);
i++; // Consume location i++; // Consume location
} else if (part === 'VIS' && i + 2 < parts.length) {
const direction = parts[i+1];
const distance = parts[i+2];
decoded.push(` - VIS ${direction} ${distance}: Sector visibility to the ${direction} of ${distance} statute miles`);
i += 2;
} else if (part === 'VIRGA' && i + 1 < parts.length && parts[i+1] === 'ALQDS') { } else if (part === 'VIRGA' && i + 1 < parts.length && parts[i+1] === 'ALQDS') {
decoded.push(` - VIRGA ALQDS: Virga in all quadrants`); decoded.push(` - VIRGA ALQDS: Virga in all quadrants`);
i++; // Consume ALQDS i++; // Consume ALQDS
@@ -289,6 +294,16 @@
const direction = directions[parts[i+1]]; const direction = directions[parts[i+1]];
decoded.push(` - VIRGA ${parts[i+1]}: Virga to the ${direction.toLowerCase()}`); decoded.push(` - VIRGA ${parts[i+1]}: Virga to the ${direction.toLowerCase()}`);
i++; // Consume direction part i++; // Consume direction part
} else if (part === 'TCU' && i + 1 < parts.length && parts[i+1] === 'ASOCTD') {
let remark = ' - TCU ASOCTD: Towering cumulus associated';
let consumed = 1;
if (i + 2 < parts.length && directions[parts[i+2]]) {
const direction = directions[parts[i+2]];
remark += ` to the ${direction.toLowerCase()}`;
consumed = 2;
}
decoded.push(remark);
i += consumed;
} else if (part === 'TCU' && i + 1 < parts.length && parts[i+1] === 'ALQDS') { } else if (part === 'TCU' && i + 1 < parts.length && parts[i+1] === 'ALQDS') {
decoded.push(` - TCU ALQDS: Towering cumulus in all quadrants`); decoded.push(` - TCU ALQDS: Towering cumulus in all quadrants`);
i++; // Consume ALQDS i++; // Consume ALQDS
@@ -319,6 +334,10 @@
if (i + 3 < parts.length && parts[i+2] === '/' && parts[i+3] === 'HALO') { if (i + 3 < parts.length && parts[i+2] === '/' && parts[i+3] === 'HALO') {
remark = ` - ${part} ASOCTD / HALO: ${cloudName} associated with Halo phenomenon`; remark = ` - ${part} ASOCTD / HALO: ${cloudName} associated with Halo phenomenon`;
consumed = 3; consumed = 3;
} else if (i + 2 < parts.length && directions[parts[i+2]]) {
const direction = directions[parts[i+2]];
remark += ` to the ${direction.toLowerCase()}`;
consumed = 2;
} }
decoded.push(remark); decoded.push(remark);
i += consumed; i += consumed;
@@ -326,6 +345,16 @@
const cloudName = cloudTypes[part]; const cloudName = cloudTypes[part];
decoded.push(` - ${part} ALQDS: ${cloudName} in all quadrants`); decoded.push(` - ${part} ALQDS: ${cloudName} in all quadrants`);
i++; // Consume ALQDS i++; // Consume ALQDS
} else if (cloudTypes[part] && i + 1 < parts.length && directions[parts[i+1]]) {
const cloudName = cloudTypes[part];
let remark = ` - ${part} ${parts[i+1]}: ${cloudName} to the ${directions[parts[i+1]].toLowerCase()}`;
let consumed = 1;
if (i + 2 < parts.length && parts[i+2] === 'MOV' && i + 3 < parts.length && directions[parts[i+3]]) {
remark += `, moving ${directions[parts[i+3]].toLowerCase()}`;
consumed = 3;
}
decoded.push(remark);
i += consumed;
} else if (specialRemarks[part]) { } else if (specialRemarks[part]) {
decoded.push(` - ${part}: ${specialRemarks[part]}`); decoded.push(` - ${part}: ${specialRemarks[part]}`);
} else if (cloudTypes[part] && i + 1 < parts.length && parts[i + 1] === 'TR') { } else if (cloudTypes[part] && i + 1 < parts.length && parts[i + 1] === 'TR') {
@@ -335,7 +364,28 @@
} else if (weatherRegex.test(part)) { } else if (weatherRegex.test(part)) {
decoded.push(` - ${decodeWeather(part, metarString)}`); decoded.push(` - ${decodeWeather(part, metarString)}`);
} else if (part.match(/^(AC|CI|CC)/)) { } else if (part.match(/^(AC|CI|CC)/)) {
decoded.push(` - ${part}: Cloud types and coverage details`); // This is a cloud-like remark, e.g. ACC for Altocumulus Castellanus
// It can have modifiers like other clouds.
const cloudName = cloudTypes[part] || part; // part will be ACC
let remark = ` - ${cloudName}: Cloud types and coverage details`;
let consumed = 0;
if (i + 1 < parts.length && parts[i+1] === 'TR') {
remark = ` - ${cloudName} TR: ${cloudName} clouds are translucent (thin)`;
consumed = 1;
} else if (i + 1 < parts.length && parts[i+1] === 'ASOCTD') {
remark = ` - ${cloudName} ASOCTD: ${cloudName} associated`;
consumed = 1;
if (i + 2 < parts.length && directions[parts[i+2]]) {
remark += ` to the ${directions[parts[i+2]].toLowerCase()}`;
consumed = 2;
}
} else if (i + 1 < parts.length && directions[parts[i+1]]) {
remark = ` - ${cloudName} ${parts[i+1]}: ${cloudName} to the ${directions[parts[i+1]].toLowerCase()}`;
consumed = 1;
}
decoded.push(remark);
i += consumed;
} else if (part === '/') { } else if (part === '/') {
// separator, ignore // separator, ignore
} else { } else {