UNPKG

flight-planner

Version:

Plan and route VFR flights

163 lines (162 loc) 6.19 kB
import { normalizeICAO } from './utils.js'; import { parseMetar } from "metar-taf-parser"; import convert from 'convert-units'; import { FlightRules } from './index.js'; /** * Creates a Metar object from a raw METAR string. * * @param raw The raw METAR string * @returns A Metar object */ export function createMetarFromString(raw) { const metar = parseMetar(raw); const observationTime = new Date(); if (metar.day) { observationTime.setUTCDate(metar.day); } else { observationTime.setUTCDate(observationTime.getUTCDate() - 1); } if (metar.hour) { observationTime.setUTCHours(metar.hour); } if (metar.minute) { observationTime.setUTCMinutes(metar.minute); } observationTime.setUTCSeconds(0); observationTime.setUTCMilliseconds(0); // TODO: This is where we do all the conversion to the correct units return { station: normalizeICAO(metar.station), observationTime, raw: metar.message, wind: { direction: metar.wind?.degrees, directionMin: metar.wind?.minVariation, directionMax: metar.wind?.maxVariation, speed: metar.wind?.speed, gust: metar.wind?.gust, }, temperature: metar.temperature, dewpoint: metar.dewPoint, visibility: metar.visibility ? { value: metar.visibility.value, unit: metar.visibility.unit === 'm' ? 'm' : 'sm', // TODO: Drop the unit, convert to meters } : undefined, qnh: metar.altimeter ? { value: metar.altimeter.value, unit: metar.altimeter.unit === 'hPa' ? 'hPa' : 'inHg' // TODO: Drop the unit, convert to hPa } : undefined, clouds: metar.clouds?.map((cloud) => ({ quantity: cloud.quantity, height: cloud.height, })), }; } export function metarCeiling(metar) { const cloudCeilingQuantity = ['BKN', 'OVC']; const clouds = metar.clouds || []; const cloudCeiling = clouds.filter(cloud => cloudCeilingQuantity.includes(cloud.quantity)).sort((a, b) => (a.height ?? 0) - (b.height ?? 0)); if (cloudCeiling.length > 0) { return cloudCeiling[0].height; } return undefined; } export function metarFlightRule(metar) { const ceiling = metarCeiling(metar); let visibilityMeters; if (metar.visibility !== undefined) { visibilityMeters = metar.visibility.value; if (metar.visibility.unit === 'sm') { // TODO: Move this to a utility function so it can be used once the METAR is parsed visibilityMeters = convert(visibilityMeters).from('mi').to('m'); } } if ((visibilityMeters !== undefined && visibilityMeters <= 1500) || (ceiling !== undefined && ceiling <= 500)) { return FlightRules.LIFR; } if ((visibilityMeters !== undefined && visibilityMeters <= 5000) || (ceiling !== undefined && ceiling <= 1000)) { return FlightRules.IFR; } if ((visibilityMeters !== undefined && visibilityMeters <= 8000) || (ceiling !== undefined && ceiling <= 3000)) { return FlightRules.MVFR; } return FlightRules.VFR; } export function isMetarExpired(metar, options = {}) { const now = new Date(); const { customMinutes, useStandardRules = true } = options; if (customMinutes !== undefined) { const expirationTime = new Date(metar.observationTime); expirationTime.setMinutes(metar.observationTime.getMinutes() + customMinutes); return now > expirationTime; } if (useStandardRules) { const isSpecial = metar.raw.includes('SPECI'); const expirationTime = new Date(metar.observationTime); const expirationMinutes = isSpecial ? 30 : 60; expirationTime.setMinutes(metar.observationTime.getMinutes() + expirationMinutes); return now > expirationTime; } // Fallback to the old behavior with a default of 60 minutes const expirationTime = new Date(metar.observationTime); expirationTime.setMinutes(metar.observationTime.getMinutes() + 60); return now > expirationTime; } export function metarFlightRuleColor(metarData) { const flightRule = metarFlightRule(metarData); switch (flightRule) { case FlightRules.VFR: return 'green'; case FlightRules.MVFR: return 'blue'; case FlightRules.IFR: return 'red'; case FlightRules.LIFR: return 'purple'; default: return 'black'; } } export function metarColorCode(metarData) { const visibility = metarData.visibility; const ceiling = metarCeiling(metarData); const windSpeed = metarData.wind?.speed; const gustSpeed = metarData.wind?.gust; let visibilityMeters; if (visibility) { visibilityMeters = visibility.value; if (visibility.unit === 'sm') { // TODO: Move this to a utility function so it can be used once the METAR is parsed visibilityMeters = convert(visibilityMeters).from('mi').to('m'); } } if ((visibilityMeters !== undefined && visibilityMeters < 800) || (ceiling !== undefined && ceiling < 200) || (windSpeed !== undefined && windSpeed > 40) || (gustSpeed !== undefined && gustSpeed > 50)) { return 'red'; } if ((visibilityMeters !== undefined && visibilityMeters < 1600) || (ceiling !== undefined && ceiling < 400) || (windSpeed !== undefined && windSpeed > 30) || (gustSpeed !== undefined && gustSpeed > 40)) { return 'amber'; } if ((visibilityMeters !== undefined && visibilityMeters < 3200) || (ceiling !== undefined && ceiling < 700) || (windSpeed !== undefined && windSpeed > 20) || (gustSpeed !== undefined && gustSpeed > 30)) { return 'yellow'; } if ((visibilityMeters !== undefined && visibilityMeters < 5000) || (ceiling !== undefined && ceiling < 1500) || (windSpeed !== undefined && windSpeed > 15) || (gustSpeed !== undefined && gustSpeed > 20)) { return 'blue'; } return 'green'; }