flight-planner
Version:
Plan and route VFR flights
254 lines (253 loc) • 10.7 kB
JavaScript
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
};
}