aerofly-missions
Version:
The Aerofly Missionsgerät converts simulator flight plan files for Aerofly FS 4, Microsoft Flight Simulator, X-Plane, GeoFS, and Garmin / Infinite Flight flight plan files. It also imports SimBrief flight plans.
291 lines (290 loc) • 12.1 kB
JavaScript
import { Quote } from "../Export/Quote.js";
import { LonLat } from "../World/LonLat.js";
import { Units } from "../World/Units.js";
import { MissionConditions } from "./MissionConditions.js";
export class MissionCheckpoint {
constructor() {
this.type = "waypoint";
this.name = "";
this.lon_lat = new LonLat(0, 0);
/**
* True course in degrees to fly from last point to this point.
* -1 on first, but seems rather unrelevant
*/
this.direction = -1;
/**
* Not official: Distance in nautical miles to fly from last point to this point.
* -1 on first
*/
this.distance = -1;
/**
* Only set on waypoint, function unknown
* Given in percentage, -1..1
*/
this.slope = 0;
/**
* Set on departure_runway, destination_runway
*/
this.length = 0;
/**
* In Hz, `111400000` is 111.4 MHz
* @see MissionCheckpoint.rawFreqency
*/
this.frequency = 0;
/**
* If waypoint is meant to be flown over. Else turn anticipation will be used.
*/
this.flyOver = false;
/**
* Not official: In kts TAS
*/
this.speed = -1;
/**
* Not official: In knots
*/
this.ground_speed = -1;
/**
* Not official: True heading to fly to correct for wind drift
*/
this.heading = -1;
this._icao_region = null;
}
/**
* Aerofly represents frequencies in Hz.
* If you want to set a frequency in MHz, use this setter.
*/
set frequency_mhz(frequency_mhz) {
this.frequency = frequency_mhz * 1000000;
}
get frequency_mhz() {
return this.frequency / 1000000;
}
/**
* Aerofly represents frequencies in Hz.
* If you want to set a frequency in KHz, use this setter.
*/
set frequency_khz(frequency_khz) {
this.frequency = frequency_khz * 1000;
}
get frequency_khz() {
return this.frequency / 1000;
}
/**
* @returns "k" for all frequencies up to 1,000 kHz
*/
get frequency_unit() {
return this.frequency > 10000000 ? "M" : "k";
}
get frequency_string() {
if (!this.frequency) {
return "";
}
const frequency_unit = this.frequency_unit;
return ((frequency_unit === "M" ? this.frequency_mhz.toFixed(2) : this.frequency_khz.toFixed()) +
" " +
frequency_unit +
"Hz");
}
set icao_region(icao_region) {
this._icao_region = icao_region;
}
/**
* @see https://en.m.wikipedia.org/wiki/ICAO_airport_code
*/
get icao_region() {
if (this._icao_region) {
return this._icao_region;
}
const airportMatch = this.name.match(/^([A-Z]{2})[A-Z]{2}$/);
if (airportMatch && airportMatch[1]) {
const specialRegionMatch = airportMatch[1].match(/^[CKUYZ]/);
if (specialRegionMatch &&
!["UA", "UB", "UC", "UD", "UG", "UK", "UM", "UT", "ZK", "ZM"].includes(airportMatch[1])) {
return specialRegionMatch[0];
}
return airportMatch[1];
}
return null;
}
/**
* @see https://aviation.stackexchange.com/questions/23743/what-is-the-difference-between-a-fix-a-waypoint-and-an-intersection
* FIX− A geographical position determined by visual reference to the surface, by reference to one or more radio NAVAIDs, by celestial plotting, or by another navigational device.
* WAYPOINT− A predetermined geographical position used for route/instrument approach definition, progress reports, published VFR routes, visual reporting points or points for transitioning and/or circumnavigating controlled and/or special use airspace, that is defined relative to a VORTAC station or in terms of latitude/longitude coordinates.
*/
get type_extended() {
if (this.type === MissionCheckpoint.TYPE_WAYPOINT) {
if (this.frequency) {
return this.frequency_unit === "M" ? MissionCheckpoint.TYPE_VOR : MissionCheckpoint.TYPE_NDB;
}
if (this.name.match(/^[A-Z]{3,5}$/)) {
switch (this.name.length) {
case 3:
return MissionCheckpoint.TYPE_VOR;
case 4:
return MissionCheckpoint.TYPE_AIRPORT;
default:
return MissionCheckpoint.TYPE_WAYPOINT;
}
}
return MissionCheckpoint.TYPE_FIX;
}
return this.type;
}
/**
* @returns boolean if the type and name are exportable to other applications because it is known there, e.g. VORs, NDBs
*/
isExportable() {
const type = this.type_extended;
return [
MissionCheckpoint.TYPE_ORIGIN,
MissionCheckpoint.TYPE_DESTINATION,
MissionCheckpoint.TYPE_NDB,
MissionCheckpoint.TYPE_VOR,
MissionCheckpoint.TYPE_WAYPOINT,
MissionCheckpoint.TYPE_AIRPORT,
].includes(type);
}
get direction_magnetic() {
if (this.direction == -1) {
return this.direction;
}
return (this.direction - this.lon_lat.magnetic_declination + 360) % 360;
}
get direction_rad() {
return ((this.direction % 360) / 180) * Math.PI;
}
get heading_magnetic() {
if (this.heading == -1) {
return this.heading;
}
return (this.heading - this.lon_lat.magnetic_declination + 360) % 360;
}
get distance_m() {
return this.distance * Units.meterPerNauticalMile;
}
get slope_deg() {
return (Math.atan(this.slope) * 180) / Math.PI;
}
/**
* In hours
*/
get time_enroute() {
return this.distance >= 0 && this.ground_speed > 0 ? this.distance / this.ground_speed : 0;
}
fromMainMcf(waypoint) {
this.type = waypoint.type;
this.name = waypoint.Identifier;
this.lon_lat = LonLat.fromMainMcf(waypoint.Position, waypoint.Elevation);
if (waypoint.Altitude[0]) {
this.lon_lat.altitude_m = Math.max(this.lon_lat.altitude_m, waypoint.Altitude[0]);
}
if (waypoint.Altitude[1]) {
this.lon_lat.altitude_m = Math.min(this.lon_lat.altitude_m, waypoint.Altitude[1]);
}
this.frequency = waypoint.NavaidFrequency;
this.length = waypoint.Length;
this.flyOver = waypoint.FlyOver;
return this;
}
/**
* Add direction and distance to this checkpont.
* May also change altitude to add separation.
* @see https://en.wikipedia.org/wiki/Flight_level
*
* @param lastLonLat LonLat of last checkpoint before this one
* @param changeHeight If not set to null, will change heights to 'IFR' if not 'VFR' given
*/
setDirectionByCoordinates(lastLonLat, changeHeight = null) {
this.direction = lastLonLat.getBearingTo(this.lon_lat);
this.heading = this.direction;
this.distance = lastLonLat.getDistanceTo(this.lon_lat);
if (changeHeight && this.type === MissionCheckpoint.TYPE_WAYPOINT) {
const altitude_ft = this.lon_lat.altitude_ft;
const direction_magnetic = this.direction_magnetic;
if (changeHeight === MissionConditions.CONDITION_VFR || changeHeight === MissionConditions.CONDITION_MVFR) {
// Separation above 3000ft MSL
if (altitude_ft > 3000 && altitude_ft < 20000) {
this.lon_lat.altitude_ft =
direction_magnetic < 180
? Math.ceil((altitude_ft - 1500) / 2000) * 2000 + 1500 // 3500, 5500, ..
: Math.ceil((altitude_ft - 500) / 2000) * 2000 + 500; // 4500, 6500, ..
}
}
else {
// IFR
this.lon_lat.altitude_ft =
direction_magnetic < 180
? Math.ceil((altitude_ft - 1000) / 2000) * 2000 + 1000 // 1000, 3000, ..
: Math.ceil(altitude_ft / 2000) * 2000; // 2000, 4000, ..
}
}
const altDifference = this.lon_lat.altitude_m - lastLonLat.altitude_m; // m
this.slope = altDifference / this.distance_m;
}
toString(index) {
let additional = "";
if (this.type === MissionCheckpoint.TYPE_WAYPOINT) {
additional += `\
<[bool][fly_over][${this.flyOver ? "true" : "false"}]>
`;
}
if (this.lon_lat.altitude_m !== 0) {
additional += `\
<[bool][alt_cst][true]>
`;
}
return `\
<[tmmission_checkpoint][element][${index}]
<[string8u][type][${Quote.tmc(this.type)}]>
<[string8u][name][${Quote.tmc(this.name)}]>
<[vector2_float64][lon_lat][${this.lon_lat}]>
<[float64][altitude][${this.lon_lat.altitude_m}]>
//<[float64][speed][${this.speed}]>
<[float64][direction][${this.direction}]>
<[float64][slope][${this.slope}]> // ${this.slope_deg.toFixed(1)} deg
<[float64][length][${this.length}]>
<[float64][frequency][${this.frequency.toFixed()}]>
${additional}\
>
`;
}
toStringTargetPlane(name = "finish") {
return `\
<[tmmission_target_plane][${name}][]
<[vector2_float64][lon_lat][${this.lon_lat.lon} ${this.lon_lat.lat}]>
<[float64][direction][${this.direction}]>
>
`;
}
hydrate(cp) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
this.type = (_a = cp.type) !== null && _a !== void 0 ? _a : this.type;
this.name = (_b = cp.name) !== null && _b !== void 0 ? _b : this.name;
this.lon_lat.magnetic_declination = (_c = cp.lon_lat.magnetic_declination) !== null && _c !== void 0 ? _c : this.lon_lat.magnetic_declination;
this.lon_lat.lon = (_d = cp.lon_lat.lon) !== null && _d !== void 0 ? _d : this.lon_lat.lon;
this.lon_lat.lat = (_e = cp.lon_lat.lat) !== null && _e !== void 0 ? _e : this.lon_lat.lat;
this.lon_lat.altitude_m = (_f = cp.lon_lat.altitude_m) !== null && _f !== void 0 ? _f : this.lon_lat.altitude_m;
this.direction = (_g = cp.direction) !== null && _g !== void 0 ? _g : this.direction;
this.distance = (_h = cp.distance) !== null && _h !== void 0 ? _h : this.direction;
this.slope = (_j = cp.slope) !== null && _j !== void 0 ? _j : this.slope;
this.length = (_k = cp.length) !== null && _k !== void 0 ? _k : this.length;
this.frequency = (_l = cp.frequency) !== null && _l !== void 0 ? _l : this.frequency;
this.speed = (_m = cp.speed) !== null && _m !== void 0 ? _m : this.speed;
this.ground_speed = (_o = cp.ground_speed) !== null && _o !== void 0 ? _o : this.ground_speed;
this.heading = (_p = cp.heading) !== null && _p !== void 0 ? _p : this.heading;
this.flyOver = (_q = cp.flyOver) !== null && _q !== void 0 ? _q : this.flyOver;
}
}
MissionCheckpoint.TYPE_ORIGIN = "origin";
MissionCheckpoint.TYPE_DEPARTURE_RUNWAY = "departure_runway";
MissionCheckpoint.TYPE_DEPARTURE = "departure";
MissionCheckpoint.TYPE_WAYPOINT = "waypoint";
MissionCheckpoint.TYPE_ARRIVAL = "arrival";
MissionCheckpoint.TYPE_APPROACH = "approach";
MissionCheckpoint.TYPE_DESTINATION_RUNWAY = "destination_runway";
MissionCheckpoint.TYPE_DESTINATION = "destination";
MissionCheckpoint.TYPE_VOR = "vor";
MissionCheckpoint.TYPE_NDB = "ndb";
MissionCheckpoint.TYPE_FIX = "fix";
MissionCheckpoint.TYPE_INTERSECTION = "intersection";
MissionCheckpoint.TYPE_AIRPORT = "airport";