UNPKG

flight-planner

Version:
393 lines (392 loc) 15.5 kB
// - VFR Minimum Fuel Reserves (SERA.OP.MPA.150/NCO.OP.125-like) // - Fuel for Alternate Aerodrome // - Crosswind Limitations // - Check wind and gust // - Check runway length // - Check weight & balance // - Minimum Safe Altitude (SERA.5005.f / NCO.OP.145): Enroute altitude > 1000 ft above highest obstacle within 500 M // - Low temparature warning // - Check for significant weather along route like thunderstorms, icing, turbulence // - Flight plan for controlled airspace // - Also check for alternate aerodrome // - Check for wind gusts import { routeTripWaypoints } from './navigation.js'; import { FlightRules } from './metar.types.js'; import { metarFlightRule, metarCeiling } from './metar.js'; import { isNight } from './sun.js'; import { MAX_LEG_DURATION_MINUTES } from './constants.js'; /** * Defines the severity level of an advisory. */ export var AdvisoryLevel; (function (AdvisoryLevel) { AdvisoryLevel["Info"] = "INFO"; AdvisoryLevel["Warning"] = "WARNING"; AdvisoryLevel["Error"] = "ERROR"; })(AdvisoryLevel || (AdvisoryLevel = {})); /** * Checks VFR minimum fuel reserves. * Assumes a 30-minute reserve for VFR day flights. * * @param routeTrip The flight plan's route trip. * @param aircraft The aircraft being used. * @param options The route options. * @returns An array of advisories related to fuel reserves. */ function checkVfrMinimumFuel(_routeTrip, aircraft, _options) { const advisories = []; if (!aircraft.fuelConsumption || aircraft.fuelConsumption <= 0) { advisories.push({ code: 'ERROR_AIRCRAFT_FUEL_CONSUMPTION_INVALID', level: AdvisoryLevel.Error, details: { fuelConsumption: aircraft.fuelConsumption }, }); return advisories; } return advisories; } /** * Checks METAR conditions at all waypoints in a route trip. * Issues advisories if conditions are IFR or LIFR. * * @param routeTrip The flight plan's route trip. * @returns An array of advisories related to waypoint weather conditions. */ function checkVfrMinimumWeatherConditions(routeTrip) { const advisories = []; const waypoints = routeTripWaypoints(routeTrip); for (const waypoint of waypoints) { const metarStation = waypoint.metarStation; if (metarStation && metarStation.metar) { const metar = metarStation.metar; const flightRule = metarFlightRule(metar); if (flightRule === FlightRules.IFR || flightRule === FlightRules.LIFR) { advisories.push({ code: 'ERROR_WEATHER_BELOW_MINIMUM', level: AdvisoryLevel.Error, details: { waypointName: waypoint.name, station: metarStation.station, flightRule: flightRule, visibility: metar.visibility, ceiling: metarCeiling(metar), }, }); } else if (flightRule === FlightRules.MVFR) { advisories.push({ code: 'WARN_WEATHER_MVFR', level: AdvisoryLevel.Warning, details: { waypointName: waypoint.name, station: metarStation.station, flightRule: flightRule, visibility: metar.visibility, ceiling: metarCeiling(metar), }, }); } } } return advisories; } /** * Checks wind conditions (headwind and crosswind) at departure and arrival waypoints. * * @param routeTrip The flight plan's route trip. * @param aircraft The aircraft being used. * @returns An array of advisories related to wind conditions. */ function checkWindLimits(routeTrip, aircraft) { const advisories = []; const departureLeg = routeTrip.route[0]; const arrivalLeg = routeTrip.route[routeTrip.route.length - 1]; // --- Check Departure Waypoint --- const departureWaypoint = departureLeg.start.waypoint; if (departureLeg.performance) { const { headWind, crossWind } = departureLeg.performance; // Crosswind check for departure if (aircraft.maxDemonstratedCrosswind !== undefined && aircraft.maxDemonstratedCrosswind > 0) { if (Math.abs(crossWind) > aircraft.maxDemonstratedCrosswind) { advisories.push({ code: 'ERROR_WIND_DEPARTURE_CROSSWIND_EXCEEDS_LIMITS', level: AdvisoryLevel.Error, details: { waypointName: departureWaypoint.name, crosswind: Math.round(crossWind), maxDemonstratedCrosswind: Math.round(aircraft.maxDemonstratedCrosswind), headwind: Math.round(headWind), }, }); } } // Headwind/Tailwind information for departure if (headWind < -5) { advisories.push({ code: 'INFO_WIND_DEPARTURE_SIGNIFICANT_TAILWIND', level: AdvisoryLevel.Info, details: { waypointName: departureWaypoint.name, tailwind: Math.round(-headWind), crosswind: Math.round(crossWind), }, }); } else if (headWind > 15) { advisories.push({ code: 'INFO_WIND_DEPARTURE_SIGNIFICANT_HEADWIND', level: AdvisoryLevel.Info, details: { waypointName: departureWaypoint.name, headwind: Math.round(headWind), crosswind: Math.round(crossWind), }, }); } } // --- Check Arrival Waypoint --- const arrivalWaypoint = arrivalLeg.end.waypoint; if (arrivalLeg.performance) { const { headWind, crossWind } = arrivalLeg.performance; // Crosswind check for arrival if (aircraft.maxDemonstratedCrosswind !== undefined && aircraft.maxDemonstratedCrosswind > 0) { if (Math.abs(crossWind) > aircraft.maxDemonstratedCrosswind) { advisories.push({ code: 'ERROR_WIND_ARRIVAL_CROSSWIND_EXCEEDS_LIMITS', level: AdvisoryLevel.Error, details: { waypointName: arrivalWaypoint.name, crosswind: Math.round(crossWind), maxDemonstratedCrosswind: Math.round(aircraft.maxDemonstratedCrosswind), headwind: Math.round(headWind), }, }); } } // Headwind/Tailwind information for arrival if (headWind < -5) { advisories.push({ code: 'INFO_WIND_ARRIVAL_SIGNIFICANT_TAILWIND', level: AdvisoryLevel.Info, details: { waypointName: arrivalWaypoint.name, tailwind: Math.round(-headWind), crosswind: Math.round(crossWind), }, }); } else if (headWind > 15) { advisories.push({ code: 'INFO_WIND_ARRIVAL_SIGNIFICANT_HEADWIND', level: AdvisoryLevel.Info, details: { waypointName: arrivalWaypoint.name, headwind: Math.round(headWind), crosswind: Math.round(crossWind), }, }); } } return advisories; } function checkTemperature(routeTrip) { const advisories = []; const waypoints = routeTripWaypoints(routeTrip); for (const waypoint of waypoints) { const metarStation = waypoint.metarStation; if (metarStation && metarStation.metar) { const metar = metarStation.metar; if (metar.temperature !== undefined) { // Check for low temperatures if (metar.temperature < -5) { advisories.push({ code: 'WARN_LOW_TEMPERATURE', level: AdvisoryLevel.Warning, details: { waypointName: waypoint.name, station: metarStation.station, temperature: metar.temperature, }, }); } // Check for high temperatures if (metar.temperature > 30) { advisories.push({ code: 'WARN_HIGH_TEMPERATURE', level: AdvisoryLevel.Warning, details: { waypointName: waypoint.name, station: metarStation.station, temperature: metar.temperature, }, }); } } } } return advisories; } /** * Checks if the planned flight altitude exceeds the aircraft's service ceiling. * * @param routeTrip The flight plan's route trip. * @param aircraft The aircraft being used for the flight. * @returns An array of advisories. */ function checkServiceCeiling(routeTrip, aircraft) { const advisories = []; if (aircraft.serviceCeiling === undefined) { return advisories; } if (aircraft.serviceCeiling <= 0) { advisories.push({ code: 'WARN_AIRCRAFT_SERVICE_CEILING_INVALID', level: AdvisoryLevel.Warning, details: { aircraftRegistration: aircraft.registration, }, }); return advisories; } const routeSegments = [routeTrip.route[0].start, ...routeTrip.route.map(leg => leg.end)]; for (const routeSegment of routeSegments) { if (routeSegment.altitude !== undefined && routeSegment.altitude > aircraft.serviceCeiling) { advisories.push({ code: 'ERROR_ALTITUDE_EXCEEDS_SERVICE_CEILING', level: AdvisoryLevel.Error, details: { waypointName: routeSegment.waypoint.name, altitude: routeSegment.altitude, serviceCeiling: aircraft.serviceCeiling, }, }); } } return advisories; } /** * Checks if the enroute altitude is above the minimum safe altitude of 500 ft. * * @param routeTrip The flight plan's route trip. * @returns An array of advisories. */ function checkMinimumSafeAltitude(routeTrip) { const advisories = []; const minimumAltitude = 500; // Iterate over enroute segments (excluding departure and arrival) const routeSegments = [routeTrip.route[0].start, ...routeTrip.route.map(leg => leg.end)]; for (let i = 1; i < routeSegments.length - 1; i++) { const routeSegment = routeSegments[i]; if (routeSegment.altitude !== undefined && routeSegment.altitude < minimumAltitude) { advisories.push({ code: 'WARN_ALTITUDE_BELOW_MINIMUM_SAFE_ENROUTE', level: AdvisoryLevel.Warning, details: { waypointName: routeSegment.waypoint.name, altitude: routeSegment.altitude, minimumSafeAltitude: minimumAltitude, }, }); } } return advisories; } function checkNightFlight(routeTrip) { const advisories = []; const firstLeg = routeTrip.route[0]; if (firstLeg && routeTrip.departureDate && isNight(firstLeg.start.waypoint, routeTrip.departureDate)) { advisories.push({ code: 'INFO_NIGHT_DEPARTURE', level: AdvisoryLevel.Info, details: { waypointName: firstLeg.start.waypoint.name, }, }); } for (const routeLeg of routeTrip.route) { if (routeLeg.arrivalDate && isNight(routeLeg.end.waypoint, routeLeg.arrivalDate)) { advisories.push({ code: 'INFO_NIGHT_WAYPOINT', level: AdvisoryLevel.Info, details: { waypointName: routeLeg.end.waypoint.name, }, }); } } if (routeTrip.routeAlternate && routeTrip.routeAlternate.end && routeTrip.routeAlternate.arrivalDate) { if (isNight(routeTrip.routeAlternate.end.waypoint, routeTrip.routeAlternate.arrivalDate)) { advisories.push({ code: 'INFO_NIGHT_ALTERNATE', level: AdvisoryLevel.Info, details: { waypointName: routeTrip.routeAlternate.end.waypoint.name, }, }); } } return advisories; } /** * Checks for excessively long flight legs. * * @param routeTrip The flight plan\'s route trip. * @returns An array of advisories related to long legs. */ function checkLongRouteLegs(routeTrip) { const advisories = []; for (const leg of routeTrip.route) { if (leg.performance && leg.performance.duration > MAX_LEG_DURATION_MINUTES) { advisories.push({ code: 'WARN_LONG_FLIGHT_LEG', level: AdvisoryLevel.Warning, details: { waypointNameStart: leg.start.waypoint.name, waypointNameEnd: leg.end.waypoint.name, durationMinutes: leg.performance.duration, maxRecommendedDurationMinutes: MAX_LEG_DURATION_MINUTES, }, }); } } return advisories; } /** * Validates a RouteTrip against various aviation regulations and best practices. * * @param routeTrip The flight plan's route trip. * @param aircraft The aircraft being used for the flight. * @param options Optional route configuration. * @returns An array of advisories. */ export function routeTripValidate(routeTrip, aircraft, options) { let allAdvisories = []; const fuelAdvisories = checkVfrMinimumFuel(routeTrip, aircraft, options); allAdvisories = allAdvisories.concat(fuelAdvisories); const weatherAdvisories = checkVfrMinimumWeatherConditions(routeTrip); allAdvisories = allAdvisories.concat(weatherAdvisories); const crosswindAdvisories = checkWindLimits(routeTrip, aircraft); allAdvisories = allAdvisories.concat(crosswindAdvisories); const temperatureAdvisories = checkTemperature(routeTrip); allAdvisories = allAdvisories.concat(temperatureAdvisories); const serviceCeilingAdvisories = checkServiceCeiling(routeTrip, aircraft); allAdvisories = allAdvisories.concat(serviceCeilingAdvisories); const minSafeAltitudeAdvisories = checkMinimumSafeAltitude(routeTrip); allAdvisories = allAdvisories.concat(minSafeAltitudeAdvisories); const nightFlightAdvisories = checkNightFlight(routeTrip); allAdvisories = allAdvisories.concat(nightFlightAdvisories); const longLegAdvisories = checkLongRouteLegs(routeTrip); allAdvisories = allAdvisories.concat(longLegAdvisories); return allAdvisories; } /** * Checks if advisories contain any errors. * * @param advisories An array of advisories to check. * @returns True if any advisory has an error level, false otherwise. */ export function advisoryHasErrors(advisories) { return advisories.some(advisory => advisory.level === AdvisoryLevel.Error); }