export const mainAirports = [ "CYHZ", "CYFC", "CYQM", "CYSJ", "CYZX", "CYYG", "CYYT", "CYQX", "CYYR", "LFVP", ]; export const secondaryAirports = ["CYQI", "CYAY", "CYDF", "CYJT"]; export const magneticVariation = 18; // West variation (Add to True Wind) export const runways = { CYHZ: { "05": 53, "32": 323, "23": 233, "14": 143 }, CYFC: { "09": 87, "15": 148, "27": 268, "33": 328 }, CYQM: { "06": 61, "29": 286, "11": 106, "24": 241 }, CYSJ: { "23": 229, "05": 49, "14": 138, "32": 319 }, CYZX: { "08": 80, "12": 122, "26": 261, "30": 303 }, CYYG: { "03": 27, "21": 207, "10": 97, "28": 277 }, CYYT: { "28": 283, "10": 103, "16": 156, "34": 336 }, CYQX: { "21": 210, "03": 30, "13": 128, "31": 308 }, CYYR: { "08": 76, "15": 154, "26": 256, "33": 334 }, LFVP: { "08": 76, "26": 256 }, CYQI: { "06": 59, "15": 150, "24": 239, "33": 330 }, CYAY: { "10": 99, "28": 279 }, CYDF: { "25": 244, "07": 64 }, CYJT: { "27": 270, "09": 90 }, } as const; export function getUTCtime() { const now = new Date(); return `${String(now.getUTCHours()).padStart(2, "0")}:${String( now.getUTCMinutes() ).padStart(2, "0")} UTC`; } export async function fetchAirportData(airportCode: string) { const url = `https://avwx.rest/api/metar/${airportCode}?token=vmtkb1D8Tuva2Jw2tXihWcKE3m2sfDJkySBZygVx82I`; try { const response = await fetch(url, { headers: { 'Accept': 'application/json', 'Authorization': 'Bearer vmtkb1D8Tuva2Jw2tXihWcKE3m2sfDJkySBZygVx82I' } }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); const windDirectionTrue = parseInt(data.wind_direction?.value || "0", 10); const windSpeed = parseInt(data.wind_speed?.value || "0", 10); const gustSpeed = parseInt(data.wind_gust?.value || windSpeed.toString(), 10); const windDirectionMag = (windDirectionTrue + magneticVariation + 360) % 360; const effectiveWindSpeed = gustSpeed > windSpeed ? gustSpeed : windSpeed; const { bestRunway, bestHeadwind, bestCrosswind } = calculateBestRunway( airportCode, windDirectionMag, effectiveWindSpeed ); return { airport: airportCode, flightRules: data.flight_rules || "Unknown", flightRulesClass: (data.flight_rules || "Unknown").toLowerCase(), bestRunway: bestRunway || "N/A", wind: `${windDirectionMag}° / ${effectiveWindSpeed}`, headwind: formatHeadwind(bestHeadwind), crosswind: bestCrosswind !== null ? Math.abs(bestCrosswind) : "N/A", altimeter: data.altimeter?.repr || "N/A", metar: sanitizeMETAR(data.raw), headwindClass: getWindClass(bestHeadwind, "headwind"), crosswindClass: getWindClass(bestCrosswind, "crosswind"), runwayClass: getRunwayClass(bestHeadwind, bestCrosswind), }; } catch (error) { console.error(`Error fetching data for ${airportCode}:`, error); return null; } } function calculateBestRunway( airportCode: string, windDirection: number, windSpeed: number ) { let bestRunway = null; let bestHeadwind = -Infinity; let bestCrosswind = null; for (const [runway, heading] of Object.entries(runways[airportCode] || {})) { const { headwind, crosswind } = calculateWindComponents( windDirection, windSpeed, heading ); if ( headwind > bestHeadwind || (headwind === bestHeadwind && Math.abs(crosswind) < Math.abs(bestCrosswind)) ) { bestHeadwind = headwind; bestCrosswind = crosswind; bestRunway = runway; } } return { bestRunway, bestHeadwind, bestCrosswind }; } function calculateWindComponents( windDirection: number, windSpeed: number, runwayHeading: number ) { const angle = ((windDirection - runwayHeading + 360) % 360) * (Math.PI / 180); return { headwind: Math.round(windSpeed * Math.cos(angle)), crosswind: Math.round(windSpeed * Math.sin(angle)), }; } function sanitizeMETAR(raw: string) { const altimeterPattern = /(A\d{4})/; const match = raw.match(altimeterPattern); return match ? raw.split(match[0])[0] + match[0] : raw; } function formatHeadwind(headwind: number) { if (headwind === -Infinity) return "N/A"; return headwind > 0 ? `+${headwind}` : headwind.toString(); } function getWindClass(windSpeed: number | null, type: "headwind" | "crosswind") { if (windSpeed === null) return ""; if (type === "headwind") { return windSpeed >= 0 ? "green" : windSpeed >= -5 ? "orange" : "red"; } if (type === "crosswind") { return Math.abs(windSpeed) > 15 ? "red" : Math.abs(windSpeed) >= 12 ? "orange" : "green"; } return ""; } function getRunwayClass(headwind: number, crosswind: number | null) { if ( getWindClass(headwind, "headwind") === "red" || getWindClass(crosswind, "crosswind") === "red" ) { return "runway-red"; } if ( getWindClass(headwind, "headwind") === "orange" || getWindClass(crosswind, "crosswind") === "orange" ) { return "runway-orange"; } return "runway-green"; }