flight-planner
Version:
Plan and route VFR flights
252 lines (251 loc) • 8.78 kB
JavaScript
import { calculateWindVector, capitalizeWords } from './utils.js';
import { bearing, bearingToAzimuth, distance } from "@turf/turf";
/**
* Represents a waypoint in the flight planning system
*
* A waypoint is a specified geographical location used for navigation purposes.
* It can be associated with a METAR weather observation station.
*
* @example
* ```typescript
* const location: WaypointLocation = { lat: 37.62, lng: -122.38 };
* const sfo = new Waypoint("KSFO", location);
* ```
*/
export class Waypoint {
name;
location;
metarStation;
/**
* @param name The name of the waypoint
* @param location The location of the waypoint
* @returns An instance of the Waypoint class
*/
constructor(name, location) {
this.name = capitalizeWords(name);
this.location = location;
}
/**
* Returns a string representation of the waypoint.
*
* @returns A string representation of the waypoint
*/
toString() {
return `${this.name}`;
}
/**
* Calculates the distance to the given waypoint.
*
* @param waypoint The waypoint to calculate the distance to
* @returns The distance in nautical miles
*/
distanceTo(waypoint) {
// const distance = turf.distance(from, to, { units: 'nauticalmiles' });
const distanceInKm = distance(this.location, waypoint.location);
const distanceInNm = distanceInKm * 0.539957; // TODO: Move to constants
return distanceInNm;
}
/**
* Calculates the heading to the given waypoint.
*
* @param waypoint The waypoint to calculate the heading to
* @returns The heading in degrees
*/
headingTo(waypoint) {
const bearingValue = bearing(this.location, waypoint.location);
return bearingToAzimuth(bearingValue);
}
}
/**
* Represents a reporting point in the flight plan.
* A reporting point is a waypoint that may be required for the flight.
*
* @extends Waypoint
*/
export class ReportingPoint extends Waypoint {
required;
/**
* @param name The name of the reporting point
* @param location The location of the reporting point
* @param required Whether the reporting point is required
* @returns An instance of the ReportingPoint class
*/
constructor(name, location, required = false) {
super(name, location);
this.required = required;
}
}
/**
* Enum representing various types of airport radio frequencies.
*
* @enum {number}
* @readonly
*/
export var FrequencyType;
(function (FrequencyType) {
FrequencyType[FrequencyType["Approach"] = 0] = "Approach";
FrequencyType[FrequencyType["APRON"] = 1] = "APRON";
FrequencyType[FrequencyType["Arrival"] = 2] = "Arrival";
FrequencyType[FrequencyType["Center"] = 3] = "Center";
FrequencyType[FrequencyType["CTAF"] = 4] = "CTAF";
FrequencyType[FrequencyType["Delivery"] = 5] = "Delivery";
FrequencyType[FrequencyType["Departure"] = 6] = "Departure";
FrequencyType[FrequencyType["FIS"] = 7] = "FIS";
FrequencyType[FrequencyType["Gliding"] = 8] = "Gliding";
FrequencyType[FrequencyType["Ground"] = 9] = "Ground";
FrequencyType[FrequencyType["Information"] = 10] = "Information";
FrequencyType[FrequencyType["Multicom"] = 11] = "Multicom";
FrequencyType[FrequencyType["Unicom"] = 12] = "Unicom";
FrequencyType[FrequencyType["Radar"] = 13] = "Radar";
FrequencyType[FrequencyType["Tower"] = 14] = "Tower";
FrequencyType[FrequencyType["ATIS"] = 15] = "ATIS";
FrequencyType[FrequencyType["Radio"] = 16] = "Radio";
FrequencyType[FrequencyType["Other"] = 17] = "Other";
FrequencyType[FrequencyType["AIRMET"] = 18] = "AIRMET";
FrequencyType[FrequencyType["AWOS"] = 19] = "AWOS";
FrequencyType[FrequencyType["Lights"] = 20] = "Lights";
FrequencyType[FrequencyType["VOLMET"] = 21] = "VOLMET";
FrequencyType[FrequencyType["AFIS"] = 22] = "AFIS";
})(FrequencyType || (FrequencyType = {}));
/**
* Defines the various types of aerodromes.
*
* @enum {number}
* @readonly
*/
export var AerodromeType;
(function (AerodromeType) {
AerodromeType[AerodromeType["Airport"] = 0] = "Airport";
AerodromeType[AerodromeType["GliderSite"] = 1] = "GliderSite";
AerodromeType[AerodromeType["AirfieldCivil"] = 2] = "AirfieldCivil";
AerodromeType[AerodromeType["InternationalAirport"] = 3] = "InternationalAirport";
AerodromeType[AerodromeType["HeliportMilitary"] = 4] = "HeliportMilitary";
AerodromeType[AerodromeType["MilitaryAerodrome"] = 5] = "MilitaryAerodrome";
AerodromeType[AerodromeType["UltraLightFlyingSite"] = 6] = "UltraLightFlyingSite";
AerodromeType[AerodromeType["HeliportCivil"] = 7] = "HeliportCivil";
AerodromeType[AerodromeType["AerodromeClosed"] = 8] = "AerodromeClosed";
AerodromeType[AerodromeType["AirportIFR"] = 9] = "AirportIFR";
AerodromeType[AerodromeType["AirfieldWater"] = 10] = "AirfieldWater";
AerodromeType[AerodromeType["LandingStrip"] = 11] = "LandingStrip";
AerodromeType[AerodromeType["AgriculturalLandingStrip"] = 12] = "AgriculturalLandingStrip";
AerodromeType[AerodromeType["Altiport"] = 13] = "Altiport";
})(AerodromeType || (AerodromeType = {}));
/**
* Converts a numeric frequency type to its enum value
*
* @param type The numeric value of the frequency type
* @returns The corresponding FrequencyType enum value
*/
export const validateFrequencyType = (type) => {
if (Object.values(FrequencyType).includes(type) && typeof type === 'number') {
return type;
}
return FrequencyType.Other;
};
/**
* Represents an aerodrome (airport) in the flight planning system.
*
* @extends Waypoint
*/
export class Aerodrome extends Waypoint {
options;
/**
* @param options The options for creating the Aerodrome instance
* @returns An instance of the Aerodrome class
*/
constructor(options) {
super(options.name, options.location);
this.options = options;
}
/**
* Returns the ICAO code of the airport.
*
* @returns The ICAO code of the airport
*/
get ICAO() {
return this.options.ICAO;
}
/**
* Returns the IATA code of the airport.
*
* @returns The IATA code of the airport
*/
get IATA() {
return this.options.IATA;
}
/**
* Returns the runways of the airport.
*
* @returns An array of runways associated with the airport
*/
get runways() {
return this.options.runways;
}
/**
* Returns the frequencies of the airport.
*
* @returns An array of frequencies associated with the airport
*/
get frequencies() {
return this.options.frequencies;
}
/**
* Returns the elevation of the airport.
*
* @returns The elevation of the airport in feet
*/
get fieldElevation() {
return this.options.elevation;
}
/**
* Returns a string representation of the airport.
*
* @returns A string representation of the airport
*/
toString() {
return `${this.name} (${this.options.ICAO})`;
}
/**
* Returns the QFE (atmospheric pressure at aerodrome elevation) value.
*
* @returns The QFE value in hPa (hectopascals), rounded to 2 decimal places,
* or undefined if elevation or QNH data is not available
*/
get QFE() {
if (!this.fieldElevation || !this.metarStation || !this.metarStation.metar.qnh) {
return undefined;
}
// TODO: Standardize units
return Math.round((this.metarStation.metar.qnh.value - (this.fieldElevation / 30)) * 100) / 100;
}
/**
* Calculates the wind vectors for all runways of the airport.
*
* @returns The wind vectors for the runways in descending order of headwind
*/
get runwayWind() {
if (!this.metarStation) {
return undefined;
}
return this.options.runways
.map(runway => Aerodrome.calculateRunwayWindVector(runway, this.metarStation.metar.wind))
.sort((a, b) => b.headwind - a.headwind);
}
/**
* Calculates the wind vector for a specific runway.
*
* @param runway The runway to calculate the wind vector for
* @param wind The current wind data from METAR
* @returns The calculated runway wind vector
* @private
*/
static calculateRunwayWindVector(runway, wind) {
const windVector = calculateWindVector(wind, runway.heading);
return {
runway,
windAngle: windVector.angle,
headwind: Math.round(windVector.headwind),
crosswind: Math.round(windVector.crosswind),
};
}
}