flight-planner
Version:
Plan and route VFR flights
183 lines (182 loc) • 6.9 kB
JavaScript
import { bbox, circle, degreesToRadians, point, radiansToDegrees } from '@turf/turf';
import { ISA_STANDARD_PRESSURE_HPA } from './constants.js';
import convert from 'convert-units';
/**
* Calculates the wind vector relative to the given true track.
*
* @param wind - An object representing the wind, containing direction and speed.
* @param trueTrack - The current true track in degrees.
* @returns An object containing the wind angle, headwind, and crosswind components.
*/
export const calculateWindVector = (wind, trueTrack) => {
if (wind.direction === undefined) {
return { angle: 0, headwind: 0, crosswind: 0 };
}
const windAngle = wind.direction - trueTrack;
const windAngleRad = degreesToRadians(windAngle);
const cosAngle = Math.cos(windAngleRad);
const sinAngle = Math.sin(windAngleRad);
const headwind = wind.speed * cosAngle;
const crosswind = wind.speed * sinAngle;
return {
angle: windAngle,
headwind: headwind,
crosswind: crosswind,
};
};
const SPECIFIC_GAS_CONSTANT_DRY_AIR = 287.05; // J/(kg·K)
const STANDARD_SEA_LEVEL_DENSITY = 1.225; // kg/m^3
const FT_PER_HPA_APPROX = 27.3;
const convertPressureAltitudeToStaticPressureHpa = (pressureAltitudeFt) => {
// Standard formula for pressure from ICAO Standard Atmosphere
const tempRatio = 1 - (0.0065 * (pressureAltitudeFt * 0.3048)) / 288.15;
return ISA_STANDARD_PRESSURE_HPA * Math.pow(tempRatio, 5.25588);
};
/**
* Calculates air density.
*
* @param oatCelsius Outside Air Temperature in Celsius.
* @param staticPressureHpa Static air pressure in hPa.
* @returns Air density in kg/m^3.
*/
const calculateAirDensity = (oatCelsius, staticPressureHpa) => {
const T_kelvin = convert(oatCelsius).from('C').to('K');
const P_pascals = staticPressureHpa * 100;
return P_pascals / (SPECIFIC_GAS_CONSTANT_DRY_AIR * T_kelvin);
};
/**
* Calculates True Airspeed.
*
* @param indicatedAltitudeFt Indicated altitude in feet.
* @param qnhHpa Altimeter setting in hPa.
* @param oatCelsius Outside Air Temperature in Celsius.
* @param kcas Knots Calibrated Airspeed (or KEAS if compressibility is negligible).
* @returns Knots True Airspeed.
*/
export const calculateTrueAirspeed = (indicatedAltitudeFt, qnhHpa, oatCelsius, kcas // Assuming this is KCAS; for low speeds, KEAS approx KCAS
) => {
const pressureDiffHpa = qnhHpa - ISA_STANDARD_PRESSURE_HPA;
const pressureAltitudeFt = indicatedAltitudeFt - (pressureDiffHpa * FT_PER_HPA_APPROX);
const staticPressureHpa = convertPressureAltitudeToStaticPressureHpa(pressureAltitudeFt);
const airDensityKgM3 = calculateAirDensity(oatCelsius, staticPressureHpa);
// For simplicity here, assuming KEAS approx = KCAS.
// A full solution might add a KCAS -> KEAS step.
const keas = kcas;
const trueAirspeed = keas * Math.sqrt(STANDARD_SEA_LEVEL_DENSITY / airDensityKgM3);
return trueAirspeed;
};
/**
* Calculates the wind correction angle for the given wind, true track, and airspeed.
*
* @param wind - An object representing the wind, containing direction and speed.
* @param trueTrack - The true track in degrees.
* @param airSpeed - The airspeed in knots.
* @returns The wind correction angle in degrees.
*/
export const calculateWindCorrectionAngle = (wind, trueTrack, airSpeed) => {
const windVector = calculateWindVector(wind, trueTrack);
const ratio = Math.max(-1, Math.min(1, windVector.crosswind / airSpeed));
const wcaInRadians = Math.asin(ratio);
const wca = radiansToDegrees(wcaInRadians);
return wca; // Positive WCA means wind from the right
};
/**
* Calculates the groundspeed for the given wind, airspeed, and heading.
*
* @param wind - An object representing the wind, containing degrees and speed.
* @param airSpeed - The airspeed in knots.
* @param heading - The heading in degrees.
* @returns The groundspeed in knots.
*/
export const calculateGroundspeed = (wind, airSpeed, heading) => {
const windVector = calculateWindVector(wind, heading);
const groundspeedSquared = Math.pow(airSpeed, 2) + Math.pow(wind.speed, 2) - 2 * airSpeed * windVector.headwind;
return Math.sqrt(groundspeedSquared);
};
/**
* Creates a bounding box around a geographic center point with a specified radius.
*
* This function generates a circular polygon around the center point and returns
* its bounding box, which can be used for geographic searches or spatial queries.
*
* @param center - The center point as a GeoJSON Position [longitude, latitude]
* @param radiusKm - The radius in kilometers to extend from the center point
* @returns A GeoJSON BBox [minLon, minLat, maxLon, maxLat] encompassing the circular area
*
* @example
* ```typescript
* // Create a bounding box 50km around Amsterdam
* const bbox = createBoundingBox([4.9041, 52.3676], 50);
* // Returns: [4.254, 51.918, 5.554, 52.817]
* ```
*/
export const createBoundingBox = (center, radiusKm) => {
const locationPoint = point(center);
const circlePolygon = circle(locationPoint, radiusKm, { units: 'kilometers' });
return bbox(circlePolygon);
};
/**
* Sorts an array of clouds by their height in ascending order.
*
* @param clouds - The array of clouds to sort
* @returns The sorted array of clouds
*/
export const sortClouds = (clouds) => {
if (!clouds || clouds.length === 0)
return [];
return [...clouds].sort((a, b) => {
if (a.height === undefined)
return 1;
if (b.height === undefined)
return -1;
return a.height - b.height;
});
};
/**
* Checks if the given string is a valid ICAO code.
*
* @param icao - The string to check
* @returns True if the string is a valid ICAO code, false otherwise
*/
export const isICAO = (icao) => {
return /^[A-Z]{4}$/.test(icao.toUpperCase());
};
/**
* Normalizes the given ICAO code to uppercase.
*
* @param icao - The ICAO code to normalize
* @returns The normalized ICAO code
*/
export const normalizeICAO = (icao) => {
return icao.toUpperCase();
};
/**
* Checks if the given string is a valid IATA code.
*
* @param iata - The string to check
* @returns True if the string is a valid IATA code, false otherwise
*/
export const isIATA = (iata) => {
return /^[A-Z]{3}$/.test(iata.toUpperCase());
};
/**
* Normalizes the given IATA code to uppercase.
*
* @param iata - The IATA code to normalize
* @returns The normalized IATA code
*/
export const normalizeIATA = (iata) => {
return iata.toUpperCase();
};
/**
* Capitalizes the first letter of each word in a string
*
* @param text - The input text to capitalize
* @returns The text with the first letter of each word capitalized
*/
export const capitalizeWords = (text) => {
return text.toLowerCase()
.split(' ')
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ');
};