162 lines
4.6 KiB
TypeScript
162 lines
4.6 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")}Z`;
|
|
}
|
|
|
|
export function calculateAirportData(airportCode: string, data: any) {
|
|
try {
|
|
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 processing 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";
|
|
} |