Compare commits
10 Commits
78a1bb6e42
...
e3b9c7901b
| Author | SHA1 | Date | |
|---|---|---|---|
| e3b9c7901b | |||
| 822a2237de | |||
| 1c884fea14 | |||
| 3015e43787 | |||
| d36a6999e2 | |||
| c7a6fa4a11 | |||
| dc7345fb1d | |||
| ce0e3f8546 | |||
| 467cb9defc | |||
| ff9f9de982 |
+85
-51
@@ -63,19 +63,19 @@
|
||||
<label style="margin-left: 1em;"><input type="checkbox" id="decode-all-checkbox"> Decode all</label>
|
||||
<div id="decoding-display"></div>
|
||||
<button id="next-btn" style="margin-top: 1em;">Next</button>
|
||||
<button id="test-all-btn">Test All METARs</button>
|
||||
</div>
|
||||
<script>
|
||||
const metarDisplay = document.getElementById('metar-display');
|
||||
const decodingDisplay = document.getElementById('decoding-display');
|
||||
const newMetarBtn = document.getElementById('new-metar-btn');
|
||||
const testAllBtn = document.getElementById('test-all-btn');
|
||||
const nextBtn = document.getElementById('next-btn');
|
||||
const decodeAllCheckbox = document.getElementById('decode-all-checkbox');
|
||||
const metars = [];
|
||||
let sortedMetars = [];
|
||||
|
||||
let currentMetarSections = [];
|
||||
let currentSectionIndex = 0;
|
||||
let currentMetarIndex = -1;
|
||||
|
||||
// --- METAR Ranking ---
|
||||
|
||||
@@ -147,9 +147,10 @@
|
||||
}
|
||||
|
||||
// Clouds
|
||||
if (part.match(/^(BKN|OVC)/)) {
|
||||
if (part.match(/^(BKN|OVC|VV)/)) {
|
||||
score += 1;
|
||||
const ceilingAlt = parseInt(part.substring(3, 6));
|
||||
const altCode = part.startsWith('VV') ? part.substring(2, 5) : part.substring(3, 6);
|
||||
const ceilingAlt = parseInt(altCode);
|
||||
if (!isNaN(ceilingAlt)) {
|
||||
const ceilingFt = ceilingAlt * 100;
|
||||
if (ceilingFt < 500) score += 8;
|
||||
@@ -285,6 +286,14 @@
|
||||
}
|
||||
|
||||
function decodeClouds(code, metarString) {
|
||||
if (code.startsWith('VV')) {
|
||||
const height = code.substring(2, 5);
|
||||
if (height === '///') {
|
||||
return `${code}: Vertical visibility sensor inoperative`;
|
||||
}
|
||||
const alt = parseInt(height) * 100;
|
||||
return `${code}: Vertical visibility ${alt.toLocaleString()} feet`;
|
||||
}
|
||||
const coverMap = {
|
||||
'SKC': 'Sky clear',
|
||||
'CLR': 'Sky clear',
|
||||
@@ -737,22 +746,31 @@
|
||||
// Section 1: Airport, Time, Auto
|
||||
let airportTimeAutoRaw = [];
|
||||
let airportTimeAutoDecoded = [];
|
||||
let preambleEnded = false;
|
||||
|
||||
if (parts[index] && parts[index].length === 4 && parts[index].match(/^[A-Z][A-Z0-9]{3}$/)) {
|
||||
airportTimeAutoRaw.push(parts[index]);
|
||||
airportTimeAutoDecoded.push(decodeAirport(parts[index], metarString));
|
||||
index++;
|
||||
}
|
||||
if (parts[index] && parts[index].endsWith('Z')) {
|
||||
airportTimeAutoRaw.push(parts[index]);
|
||||
airportTimeAutoDecoded.push(decodeTime(parts[index]));
|
||||
index++;
|
||||
}
|
||||
if (parts[index] && parts[index] === 'AUTO') {
|
||||
airportTimeAutoRaw.push(parts[index]);
|
||||
airportTimeAutoDecoded.push("AUTO: Fully automated observation");
|
||||
index++;
|
||||
while(index < parts.length && !preambleEnded) {
|
||||
const part = parts[index];
|
||||
if (part === 'AUTO') {
|
||||
airportTimeAutoRaw.push(part);
|
||||
airportTimeAutoDecoded.push("AUTO: Fully automated observation");
|
||||
index++;
|
||||
} else if (part.length === 4 && part.match(/^[A-Z][A-Z0-9]{3}$/)) {
|
||||
airportTimeAutoRaw.push(part);
|
||||
airportTimeAutoDecoded.push(decodeAirport(part, metarString));
|
||||
index++;
|
||||
} else if (part.endsWith('Z')) {
|
||||
airportTimeAutoRaw.push(part);
|
||||
airportTimeAutoDecoded.push(decodeTime(part));
|
||||
index++;
|
||||
} else if (part.match(/^CC[A-Z]$/)) {
|
||||
airportTimeAutoRaw.push(part);
|
||||
airportTimeAutoDecoded.push(`${part}: Correction to the report`);
|
||||
index++;
|
||||
} else {
|
||||
preambleEnded = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (airportTimeAutoRaw.length > 0) {
|
||||
sections.push({
|
||||
raw: airportTimeAutoRaw.join(' '),
|
||||
@@ -782,7 +800,7 @@
|
||||
sections.push({ raw: part, decoded: decodeVisibility(part) });
|
||||
} else if (part.match(/^([+-]|VC)?(MI|PR|BC|DR|BL|SH|TS|FZ|DZ|RA|SN|SG|IC|PL|GR|GS|UP|BR|FG|FU|VA|DU|SA|HZ|PY|PO|SQ|FC|SS|DS|TR)+$/)) {
|
||||
sections.push({ raw: part, decoded: decodeWeather(part, metarString) });
|
||||
} else if (part.match(/^(SKC|CLR|FEW|SCT|BKN|OVC)/)) {
|
||||
} else if (part.match(/^(VV|SKC|CLR|FEW|SCT|BKN|OVC)/)) {
|
||||
sections.push({ raw: part, decoded: decodeClouds(part, metarString) });
|
||||
} else if (part.match(/^(M?\d{2})\/(M?\d{2})$/)) {
|
||||
sections.push({ raw: part, decoded: decodeTempDew(part) });
|
||||
@@ -808,25 +826,35 @@
|
||||
return sections;
|
||||
}
|
||||
|
||||
function displayMetar(index) {
|
||||
function displayMetar(id) {
|
||||
if (metars.length === 0) {
|
||||
metarDisplay.textContent = 'No METARs loaded.';
|
||||
return;
|
||||
}
|
||||
|
||||
let metarIndex;
|
||||
if (index !== undefined && index >= 0 && index < metars.length) {
|
||||
metarIndex = index;
|
||||
} else {
|
||||
// Bias random selection towards the more interesting (higher-scored) METARs at the start of the array.
|
||||
// Math.random() * Math.random() skews distribution towards 0.
|
||||
metarIndex = Math.floor(Math.random() * Math.random() * metars.length);
|
||||
|
||||
// If an ID is provided, try to find it.
|
||||
if (id !== undefined) {
|
||||
metarIndex = metars.findIndex(m => m.line === id);
|
||||
}
|
||||
|
||||
const metarString = metars[metarIndex].metar;
|
||||
history.replaceState(null, '', '#' + metarIndex);
|
||||
// If no ID was provided, or the ID was not found, pick a random METAR.
|
||||
if (id === undefined || metarIndex === -1) {
|
||||
const randomSortedIndex = Math.floor(Math.random() * Math.random() * sortedMetars.length);
|
||||
const selectedMetar = sortedMetars[randomSortedIndex];
|
||||
metarIndex = metars.findIndex(m => m.metar === selectedMetar.metar);
|
||||
}
|
||||
|
||||
currentMetarSections = generateMetarSections(metarString);
|
||||
if (metarIndex === currentMetarIndex) {
|
||||
return; // Already displaying this METAR
|
||||
}
|
||||
currentMetarIndex = metarIndex;
|
||||
|
||||
const metarData = metars[metarIndex];
|
||||
history.replaceState(null, '', '#' + metarData.line);
|
||||
|
||||
currentMetarSections = generateMetarSections(metarData.metar);
|
||||
currentSectionIndex = 0;
|
||||
updateDisplay();
|
||||
}
|
||||
@@ -836,11 +864,11 @@
|
||||
.then(data => {
|
||||
const lines = data.split('\n');
|
||||
const rawMetars = [];
|
||||
lines.forEach(line => {
|
||||
lines.forEach((line, index) => {
|
||||
if (line.includes('METAR')) {
|
||||
const metarString = line.substring(line.indexOf('METAR') + 6).replace('=', '').trim();
|
||||
if (metarString) {
|
||||
rawMetars.push(metarString);
|
||||
rawMetars.push({ metar: metarString, line: index + 1 });
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -849,33 +877,34 @@
|
||||
|
||||
// 1. Calculate component frequencies for rarity scoring
|
||||
const componentFrequencies = {};
|
||||
rawMetars.forEach(metarString => {
|
||||
const parts = getMetarParts(metarString);
|
||||
rawMetars.forEach(metarObj => {
|
||||
const parts = getMetarParts(metarObj.metar);
|
||||
parts.forEach(part => {
|
||||
componentFrequencies[part] = (componentFrequencies[part] || 0) + 1;
|
||||
});
|
||||
});
|
||||
|
||||
// 2. Score and store each METAR
|
||||
rawMetars.forEach(metarString => {
|
||||
const score = calculateMetarScore(metarString, componentFrequencies, rawMetars.length);
|
||||
metars.push({ metar: metarString, score: score });
|
||||
rawMetars.forEach(metarObj => {
|
||||
const score = calculateMetarScore(metarObj.metar, componentFrequencies, rawMetars.length);
|
||||
metars.push({ metar: metarObj.metar, score: score, line: metarObj.line });
|
||||
});
|
||||
|
||||
// 3. Sort by score descending
|
||||
metars.sort((a, b) => b.score - a.score);
|
||||
// 3. Create a sorted copy for biased random selection, but don't sort the main array
|
||||
sortedMetars = [...metars];
|
||||
sortedMetars.sort((a, b) => b.score - a.score);
|
||||
|
||||
console.log(`Loaded and ranked ${metars.length} METARs.`);
|
||||
console.log(`Top 5 most interesting METARs:`);
|
||||
for (let i = 0; i < Math.min(5, metars.length); i++) {
|
||||
console.log(` - Score: ${metars[i].score.toFixed(2)}, METAR: ${metars[i].metar}`);
|
||||
for (let i = 0; i < Math.min(5, sortedMetars.length); i++) {
|
||||
console.log(` - Score: ${sortedMetars[i].score.toFixed(2)}, METAR: ${sortedMetars[i].metar}`);
|
||||
}
|
||||
|
||||
const hash = window.location.hash.substring(1);
|
||||
const initialIndex = parseInt(hash, 10);
|
||||
const initialId = parseInt(hash, 10);
|
||||
|
||||
if (!isNaN(initialIndex) && initialIndex >= 0 && initialIndex < metars.length) {
|
||||
displayMetar(initialIndex);
|
||||
if (!isNaN(initialId)) {
|
||||
displayMetar(initialId);
|
||||
} else {
|
||||
displayMetar(); // Display initial random METAR
|
||||
}
|
||||
@@ -887,6 +916,13 @@
|
||||
|
||||
newMetarBtn.addEventListener('click', () => displayMetar());
|
||||
decodeAllCheckbox.addEventListener('change', updateDisplay);
|
||||
window.addEventListener('hashchange', () => {
|
||||
const hash = window.location.hash.substring(1);
|
||||
const id = parseInt(hash, 10);
|
||||
if (!isNaN(id)) {
|
||||
displayMetar(id);
|
||||
}
|
||||
});
|
||||
|
||||
function updateDisplay() {
|
||||
if (currentMetarSections.length === 0) return;
|
||||
@@ -924,7 +960,7 @@
|
||||
}
|
||||
});
|
||||
|
||||
function testAllMetars() {
|
||||
window.testAllMetars = function() {
|
||||
console.log(`--- Starting test of ${metars.length} METARs ---`);
|
||||
let failed = 0;
|
||||
let withUnknowns = 0;
|
||||
@@ -937,22 +973,22 @@
|
||||
otherRemarks: new Set(),
|
||||
};
|
||||
|
||||
metars.forEach(metarString => {
|
||||
metars.forEach(metarData => {
|
||||
const tempLogs = [];
|
||||
console.log = (...args) => {
|
||||
tempLogs.push(args.join(' '));
|
||||
};
|
||||
|
||||
try {
|
||||
const sections = generateMetarSections(metarString);
|
||||
if (sections.length === 0 && metarString.length > 0) {
|
||||
const sections = generateMetarSections(metarData.metar);
|
||||
if (sections.length === 0 && metarData.metar.length > 0) {
|
||||
throw new Error("Parser produced no sections");
|
||||
}
|
||||
|
||||
const hasUnknowns = tempLogs.some(msg => msg.includes('Unknown') || msg.includes('Other remark'));
|
||||
if (hasUnknowns) {
|
||||
withUnknowns++;
|
||||
originalConsoleLog(`METAR with decoding issues: ${metarString}`);
|
||||
originalConsoleLog(`METAR with decoding issues: ${metarData.metar}`);
|
||||
tempLogs.forEach(msg => {
|
||||
originalConsoleLog(` -> ${msg}`);
|
||||
if (msg.startsWith('Unknown Airport code:')) {
|
||||
@@ -975,7 +1011,7 @@
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
console.error("Failed to parse:", metarString, e);
|
||||
console.error("Failed to parse:", metarData, e);
|
||||
failed++;
|
||||
}
|
||||
});
|
||||
@@ -998,8 +1034,6 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
testAllBtn.addEventListener('click', testAllMetars);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+5685
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user