UNPKG

flight-planner

Version:

Plan and route VFR flights

254 lines (253 loc) 10.7 kB
import { StandardTemperature } from './index.js'; /** * Enum defining different flight phases used in performance calculations. * * @enum {string} * @readonly */ export var FlightPhase; (function (FlightPhase) { FlightPhase["Taxi"] = "taxi"; FlightPhase["Takeoff"] = "takeoff"; FlightPhase["Climb"] = "climb"; FlightPhase["Cruise"] = "cruise"; FlightPhase["Descent"] = "descent"; FlightPhase["Approach"] = "approach"; FlightPhase["Landing"] = "landing"; })(FlightPhase || (FlightPhase = {})); /** * Calculates the standard rate of climb for an aircraft based on its type. * * @param aircraft - The aircraft object containing type and performance data * @param pressureAltitude - Pressure altitude in feet * @param temperature - Temperature in Celsius * @returns The standard rate of climb in feet per minute */ export function calculateRateOfClimb(aircraft, pressureAltitude = 0, temperature = StandardTemperature) { if (!aircraft.engineType) { // Default value if engine type is unknown return 500; } // Base climb rates by engine type (approximate averages) const baseClimbRates = { 'piston': 700, 'turboprop': 1200, 'turbojet': 1500, 'turbofan': 2000, 'electric': 600, 'turboshaft': 1000 }; const baseRate = baseClimbRates[aircraft.engineType] || 700; // Reduce climb rate as altitude increases const altitudeFactor = Math.max(0.5, 1 - (pressureAltitude / 30000)); // Adjust for temperature (higher temps = lower performance) const tempDiff = temperature - StandardTemperature; const tempFactor = Math.max(0.7, 1 - (tempDiff * 0.02)); return baseRate * altitudeFactor * tempFactor; } /** * Calculates the standard rate of descent for an aircraft based on its type. * * @param aircraft - The aircraft object containing type and performance data * @returns The standard rate of descent in feet per minute (returned as a negative number) */ export function calculateRateOfDescent(aircraft) { if (!aircraft.engineType) { // Default value if engine type is unknown return -500; } // Base descent rates by engine type (approximate averages) const baseDescentRates = { 'piston': -500, 'turboprop': -800, 'turbojet': -1000, 'turbofan': -1000, 'electric': -500, 'turboshaft': -800 }; return baseDescentRates[aircraft.engineType] || -500; } /** * Creates a default performance profile for an aircraft based on its characteristics. * * @param aircraft - The aircraft object to create a performance profile for * @returns A performance profile with estimated values based on aircraft type */ export function createDefaultPerformanceProfile(aircraft) { if (!aircraft.cruiseSpeed || !aircraft.fuelConsumption) { throw new Error('Aircraft must have cruiseSpeed and fuelConsumption defined'); } // Create performance estimates based on aircraft type const cruiseSpeed = aircraft.cruiseSpeed; const baseFuelFlow = aircraft.fuelConsumption; // Estimate performance for each phase const phasePerformance = { [FlightPhase.Taxi]: { fuelFlow: baseFuelFlow * 0.3, speed: 0, duration: 10 }, [FlightPhase.Takeoff]: { fuelFlow: baseFuelFlow * 1.2, speed: cruiseSpeed * 0.3, duration: 2 }, [FlightPhase.Climb]: { fuelFlow: baseFuelFlow * 1.1, speed: cruiseSpeed * 0.8, duration: 15, rateOfClimbDescent: calculateRateOfClimb(aircraft) }, [FlightPhase.Cruise]: { fuelFlow: baseFuelFlow, speed: cruiseSpeed, duration: 60 // Will be overridden in calculations }, [FlightPhase.Descent]: { fuelFlow: baseFuelFlow * 0.6, speed: cruiseSpeed * 0.9, duration: 15, rateOfClimbDescent: calculateRateOfDescent(aircraft) }, [FlightPhase.Approach]: { fuelFlow: baseFuelFlow * 0.7, speed: cruiseSpeed * 0.5, duration: 5 }, [FlightPhase.Landing]: { fuelFlow: baseFuelFlow * 0.4, speed: cruiseSpeed * 0.25, duration: 3 } }; // Create a complete performance profile return { phasePerformance, reserveFuel: baseFuelFlow * 0.5, // 30 minutes worth of fuel reserveFuelTime: 30, climbFuelAddition: 0.1, // 10% extra for climb contingencyFuel: 0.05 // 5% contingency }; } /** * Calculates distance covered during climb. * * @param aircraft - The aircraft object * @param cruiseAltitude - Target cruise altitude in feet * @param profile - Performance profile for the aircraft * @returns Distance covered during climb in nautical miles */ export function calculateClimbDistance(aircraft, cruiseAltitude, profile) { const climbPerf = profile.phasePerformance[FlightPhase.Climb]; const rateOfClimb = climbPerf.rateOfClimbDescent || calculateRateOfClimb(aircraft); // Calculate time to climb to cruise altitude in hours const timeToClimbHours = cruiseAltitude / (rateOfClimb * 60); // Calculate distance covered during climb return timeToClimbHours * climbPerf.speed; } /** * Calculates distance covered during descent. * * @param aircraft - The aircraft object * @param cruiseAltitude - Starting cruise altitude in feet * @param profile - Performance profile for the aircraft * @returns Distance covered during descent in nautical miles */ export function calculateDescentDistance(aircraft, cruiseAltitude, profile) { const descentPerf = profile.phasePerformance[FlightPhase.Descent]; const rateOfDescent = descentPerf.rateOfClimbDescent || calculateRateOfDescent(aircraft); // Convert rate of descent to positive value for calculation const absRateOfDescent = Math.abs(rateOfDescent); // Calculate time to descend from cruise altitude in hours const toDescentHours = cruiseAltitude / (absRateOfDescent * 60); // Calculate distance covered during descent return toDescentHours * descentPerf.speed; } /** * Calculates detailed performance for a route segment. * * @param distance - Total distance in nautical miles * @param aircraft - The aircraft being used * @param cruiseAltitude - Cruise altitude in feet * @param options - Optional calculation parameters * @returns Detailed flight performance data */ export function calculateDetailedPerformance(distance, aircraft, cruiseAltitude = 3000, options = {}) { // Use provided performance profile or create a default one const profile = options.performanceProfile || createDefaultPerformanceProfile(aircraft); // Extract options with defaults const departureTime = options.departureTime || new Date(); const headwind = options.headwind || 0; // const temperature = options.temperature || StandardTemperature; // const pressureAltitude = options.pressureAltitude || cruiseAltitude; const alternateDistance = options.alternateDistance || 0; // Calculate distance segments const climbDistance = calculateClimbDistance(aircraft, cruiseAltitude, profile); const descentDistance = calculateDescentDistance(aircraft, cruiseAltitude, profile); const cruiseDistance = Math.max(0, distance - climbDistance - descentDistance); // Calculate time segments (accounting for headwind) const windFactor = aircraft.cruiseSpeed ? (aircraft.cruiseSpeed - headwind) / aircraft.cruiseSpeed : 1; const taxiTime = profile.phasePerformance[FlightPhase.Taxi].duration; const takeoffTime = profile.phasePerformance[FlightPhase.Takeoff].duration; const climbSpeed = profile.phasePerformance[FlightPhase.Climb].speed * windFactor; const climbTime = (climbDistance / climbSpeed) * 60; const cruiseSpeed = profile.phasePerformance[FlightPhase.Cruise].speed * windFactor; const cruiseTime = (cruiseDistance / cruiseSpeed) * 60; const descentSpeed = profile.phasePerformance[FlightPhase.Descent].speed * windFactor; const descentTime = (descentDistance / descentSpeed) * 60; const approachTime = profile.phasePerformance[FlightPhase.Approach].duration; const landingTime = profile.phasePerformance[FlightPhase.Landing].duration; const totalFlightTime = climbTime + cruiseTime + descentTime + approachTime + landingTime; const totalTime = taxiTime + takeoffTime + totalFlightTime; // Calculate fuel segments const taxiFuel = (taxiTime / 60) * profile.phasePerformance[FlightPhase.Taxi].fuelFlow; const takeoffFuel = (takeoffTime / 60) * profile.phasePerformance[FlightPhase.Takeoff].fuelFlow; const climbFuel = (climbTime / 60) * profile.phasePerformance[FlightPhase.Climb].fuelFlow * (1 + profile.climbFuelAddition); const cruiseFuel = (cruiseTime / 60) * profile.phasePerformance[FlightPhase.Cruise].fuelFlow; const descentFuel = (descentTime / 60) * profile.phasePerformance[FlightPhase.Descent].fuelFlow; const approachFuel = (approachTime / 60) * profile.phasePerformance[FlightPhase.Approach].fuelFlow; const landingFuel = (landingTime / 60) * profile.phasePerformance[FlightPhase.Landing].fuelFlow; const tripFuel = taxiFuel + takeoffFuel + climbFuel + cruiseFuel + descentFuel + approachFuel + landingFuel; const contingencyFuel = tripFuel * profile.contingencyFuel; // Calculate alternate and reserve fuel const alternateFuel = alternateDistance > 0 && aircraft.fuelConsumption ? (alternateDistance / cruiseSpeed) * aircraft.fuelConsumption : 0; const reserveFuel = profile.reserveFuel; const totalFuelRequired = tripFuel + contingencyFuel + alternateFuel + reserveFuel; // Calculate arrival time const arrivalTime = new Date(departureTime.getTime() + totalTime * 60 * 1000); return { distance: { climbDistance, cruiseDistance, descentDistance, totalDistance: distance }, time: { taxiTime, takeoffTime, climbTime, cruiseTime, descentTime, approachTime, landingTime, totalTime }, fuel: { taxiFuel, takeoffFuel, climbFuel, cruiseFuel, descentFuel, approachFuel, landingFuel, contingencyFuel, reserveFuel, alternateFuel, totalTripFuel: tripFuel + contingencyFuel, totalFuelRequired }, departureTime, arrivalTime }; }