2024-11-14 15:41:47 -05:00

176 lines
5.0 KiB
TypeScript

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";
}