UNPKG

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.

983 lines (888 loc) 34.2 kB
import { Quote } from "../Export/Quote.js"; import { GarminFpl } from "../Import/GarminFpl.js"; import { LonLat } from "../World/LonLat.js"; import { Units } from "../World/Units.js"; import { Aircraft, AircraftFinder } from "./Aircraft.js"; import { FileParser } from "./FileParser.js"; import { MainMcf } from "./MainMcf.js"; import { MissionCheckpoint, MissionCheckpointType } from "./MissionCheckpoint.js"; import { MissionConditions, MissionConditionsFlightRules } from "./MissionConditions.js"; export type MissionFlightSetting = | "cold_and_dark" | "before_start" | "taxi" | "takeoff" | "cruise" | "approach" | "landing" | "winch_launch" | "aerotow" | "pushback"; export class Mission { /** * This string should not be longer than MAX_LENGTH_TITLE characters to fit on the screen. */ protected _title: string = ""; /** * This string should not be longer than MAX_LENGTH_DESCRIPTION characters to fit on the screen. */ protected _description: string = ""; flight_setting: MissionFlightSetting = Mission.FLIGHT_SETTING_TAXI; /** * Internal Aerofly name of aircraft type. */ protected _aircraft_name: string = "c172"; protected _aircraft_icao: string = "C172"; /** * @see https://en.wikipedia.org/wiki/Aviation_call_signs * @see https://en.wikipedia.org/wiki/List_of_aircraft_registration_prefixes * @see https://en.wikipedia.org/wiki/List_of_airline_codes * @see http://c-aviation.net/military-callsigns/ */ callsign: string = "N5472R"; origin_icao: string = ""; origin_lon_lat: LonLat = new LonLat(0, 0); /** * True heading of aircraft in Degrees on startup */ origin_dir: number = 0; destination_icao: string = ""; destination_lon_lat: LonLat = new LonLat(0, 0); /** * True heading of aircraft in Degrees on exit */ destination_dir: number = 0; finish: MissionCheckpoint | null = null; conditions: MissionConditions = new MissionConditions(); checkpoints: MissionCheckpoint[] = []; /** * Not official: In kts TAS */ cruise_speed: number = 122; /** * Not official: In meters */ cruise_altitude: number = 0; /** * How many minutes does it take to make a full circle */ turn_time: number = 2; /** * Hide guides in mission */ no_guides: boolean = false; protected _magnetic_declination?: number; static FLIGHT_SETTING_COLD_AND_DARK: MissionFlightSetting = "cold_and_dark"; static FLIGHT_SETTING_BEFORE_START: MissionFlightSetting = "before_start"; static FLIGHT_SETTING_LANDING: MissionFlightSetting = "landing"; static FLIGHT_SETTING_TAKEOFF: MissionFlightSetting = "takeoff"; static FLIGHT_SETTING_APPROACH: MissionFlightSetting = "approach"; static FLIGHT_SETTING_TAXI: MissionFlightSetting = "taxi"; static FLIGHT_SETTING_CRUISE: MissionFlightSetting = "cruise"; static MAX_LENGTH_TITLE = 32; static MAX_LENGTH_DESCRIPTION = 50; static MAX_LINES_DESCRIPTION = 8; warnings: string[] = []; constructor(title: string, description: string) { this.title = title; this.description = description; } set title(title: string) { title = title.trim(); if (title.length > Mission.MAX_LENGTH_TITLE) { this.warnings.push(`Title is longer than ${Mission.MAX_LENGTH_TITLE}, truncating`); } this._title = title; } get title(): string { return this._title; } set description(description: string) { description = description.trim(); const lines = description.split(/\n/); let lineCount = lines.length; lines.forEach((l) => { lineCount += Math.floor(l.length / Mission.MAX_LENGTH_DESCRIPTION); }); if (lineCount > Mission.MAX_LINES_DESCRIPTION) { this.warnings.push( `Description is longer than ${Mission.MAX_LINES_DESCRIPTION} lines à ${Mission.MAX_LENGTH_DESCRIPTION} characters` ); } this._description = description; } get description(): string { return this._description; } get cruise_altitude_ft() { return this.cruise_altitude * Units.feetPerMeter; } set cruise_altitude_ft(cruise_altitude_ft: number) { this.cruise_altitude = cruise_altitude_ft / Units.feetPerMeter; } /** * @see this.setAircraft */ set aircraft_name(aircraft_name: string) { const aircraft = AircraftFinder.getByAeroflyCode(aircraft_name.toLowerCase()); this.setAircraft(aircraft); } get aircraft_name() { return this._aircraft_name; } /** * @see this.setAircraft */ set aircraft_icao(aircraft_icao: string) { const aircraft = AircraftFinder.getByIcaoCode(aircraft_icao.toUpperCase()); this.setAircraft(aircraft); } /** * @param aircraft will set all relevant properties in this missoion */ setAircraft(aircraft: Aircraft) { this._aircraft_name = aircraft.aeroflyCode; this._aircraft_icao = aircraft.icaoCode; this.callsign = aircraft.callsign; this.cruise_speed = aircraft.cruiseSpeedKts; this.cruise_altitude_ft = aircraft.cruiseAltitudeFt; this.turn_time = aircraft.turnTime; this.syncCruiseSpeed(); this.calculateCheckpoints(); } /** * @see https://www.icao.int/publications/doc8643/pages/search.aspx */ get aircraft_icao() { return this._aircraft_icao; } get origin_country() { return this.icaoAirportToIsoCountry(this.origin_icao); } get destination_country() { return this.icaoAirportToIsoCountry(this.destination_icao); } /** * In hours */ get time_enroute(): number { let total_time_enroute = 0; this.checkpoints.forEach((c) => { total_time_enroute += c.time_enroute; }); return total_time_enroute; } /** * In nautical miles */ get distance(): number { let total_distance = 0; this.checkpoints.forEach((c) => { total_distance += c.distance; }); return total_distance; } get hasFrequencies(): boolean { this.checkpoints.forEach((cp) => { if (cp.frequency) { return true; } }); return false; } fromMainMcf(mainMcf: MainMcf, ils: number = 0, withoutCheckpoints = false): Mission { this.aircraft_name = mainMcf.aircraft.name; this.cruise_altitude = mainMcf.navigation.Route.CruiseAltitude; if (!withoutCheckpoints) { switch (mainMcf.flight_setting.configuration) { case "ShortFinal": this.flight_setting = Mission.FLIGHT_SETTING_LANDING; break; case "Takeoff": this.flight_setting = Mission.FLIGHT_SETTING_TAKEOFF; break; case "Final": this.flight_setting = Mission.FLIGHT_SETTING_APPROACH; break; case "Parking": this.flight_setting = Mission.FLIGHT_SETTING_TAXI; break; default: this.flight_setting = mainMcf.flight_setting.on_ground ? Mission.FLIGHT_SETTING_TAXI : Mission.FLIGHT_SETTING_CRUISE; break; } this.conditions.fromMainMcf(mainMcf); this.finish = null; let lastPosition: LonLat | null = null; this.checkpoints = mainMcf.navigation.Route.Ways.filter((w) => { // Please not that procedure waypoints cannot be restored as of now return [ MissionCheckpoint.TYPE_ORIGIN, MissionCheckpoint.TYPE_DEPARTURE_RUNWAY, //MissionCheckpoint.TYPE_DEPARTURE, MissionCheckpoint.TYPE_WAYPOINT, //MissionCheckpoint.TYPE_ARRIVAL, //MissionCheckpoint.TYPE_APPROACH, MissionCheckpoint.TYPE_DESTINATION_RUNWAY, MissionCheckpoint.TYPE_DESTINATION, ].includes(w.type); // Filtering departure, approach and arrival - these points have no coordinates }).map((w) => { const cp = new MissionCheckpoint(); cp.fromMainMcf(w); if (lastPosition && (isNaN(cp.lon_lat.lon) || isNaN(cp.lon_lat.lat))) { cp.lon_lat = lastPosition.getRelativeCoordinates(3, 45); } lastPosition = cp.lon_lat; return cp; }); const flight_category = this.conditions.getFlightCategory(this.origin_country !== "US"); this.syncCruiseSpeed(); this.calculateCheckpoints(); this.origin_icao = this.checkpoints[0].name; this.origin_lon_lat = LonLat.fromMainMcf(mainMcf.flight_setting.position); const checkpointDepartureRunway = this.checkpoints.find((c) => { return c.type === MissionCheckpoint.TYPE_DEPARTURE_RUNWAY; }); const distanceOriginAircraft = this.origin_lon_lat.getDistanceTo(this.checkpoints[0].lon_lat); if (distanceOriginAircraft > 2) { this.warnings.push( `Position of aircraft too far away from origin of flight plan: ${distanceOriginAircraft.toFixed(2)} NM` ); if (checkpointDepartureRunway) { this.origin_lon_lat = new LonLat( checkpointDepartureRunway.lon_lat.lon, checkpointDepartureRunway.lon_lat.lat, checkpointDepartureRunway.lon_lat.altitude_m ); this.warnings.push(`Setting positon of aircraft to departure runway: ${this.origin_lon_lat}`); this.origin_dir = (checkpointDepartureRunway.direction + 180) % 360; this.warnings.push(`Setting orientation of aircraft to departure runway: ${this.origin_dir.toFixed()}°`); } } if (this.origin_dir < 0) { this.origin_dir = ((Math.atan2(mainMcf.flight_setting.orientation[1], mainMcf.flight_setting.orientation[0]) - 1) * (180 / Math.PI) + 26 + 360) % 360; this.warnings.push( `Aircraft orientation inferred from mainMcf.flight_setting.orientation: ${this.origin_dir.toFixed()}°` ); } const checkpointDestination = this.findCheckPointByType(MissionCheckpoint.TYPE_DESTINATION) ?? this.checkpoints[this.checkpoints.length - 1]; this.destination_icao = structuredClone(checkpointDestination.name); this.destination_dir = structuredClone(checkpointDestination.direction); this.destination_lon_lat = checkpointDestination.lon_lat.clone(); const checkpointDestinationRunway = this.findCheckPointByType(MissionCheckpoint.TYPE_DESTINATION_RUNWAY) ?? checkpointDestination; if (ils) { checkpointDestinationRunway.frequency_mhz = ils; } this.setAutoTitleDescription(flight_category); } return this; } fromGarminFpl(gpl: GarminFpl): Mission { if (gpl.waypoints.length < 2) { throw new Error("Not enough waypoints in flight plan"); } if (gpl.cruisingAltFt) { this.cruise_altitude_ft = gpl.cruisingAltFt; } this.flight_setting = gpl.waypoints.at(0)?.type === "AIRPORT" ? Mission.FLIGHT_SETTING_TAXI : Mission.FLIGHT_SETTING_CRUISE; this.finish = null; this.checkpoints = gpl.waypoints.map((w, i) => { const cp = new MissionCheckpoint(); cp.lon_lat.lat = w.lat; cp.lon_lat.lon = w.lon; cp.lon_lat.altitude_m = w.elevationMeter ?? 0; cp.name = w.identifier; if (w.type === "AIRPORT" && (i === 0 || i === gpl.waypoints.length - 1)) { cp.type = i === 0 ? MissionCheckpoint.TYPE_ORIGIN : MissionCheckpoint.TYPE_DESTINATION; } else if ( w.type === "USER WAYPOINT" && (i === 1 || i === gpl.waypoints.length - 2) && cp.name.match(/^(RW)?\d\d[A-Z]?$/) ) { cp.type = i === 1 ? MissionCheckpoint.TYPE_DEPARTURE_RUNWAY : MissionCheckpoint.TYPE_DESTINATION_RUNWAY; cp.name = cp.name.replace(/^(RW)/, ""); } if (w.countryCode) { cp.icao_region = w.countryCode; } return cp; }); const flight_category = this.conditions.getFlightCategory(this.origin_country !== "US"); this.syncCruiseSpeed(); this.calculateCheckpoints(); this.setAutoTitleDescription(flight_category); // Find runways and runway directions const departureRunway: MissionCheckpoint | undefined = this.findCheckPointByType( MissionCheckpoint.TYPE_DEPARTURE_RUNWAY ); const departureRunwayDirection: number | undefined = departureRunway ? Number(departureRunway.name.replace(/\D+/, "") + "0") : undefined; const destinationRunway: MissionCheckpoint | undefined = this.findCheckPointByType( MissionCheckpoint.TYPE_DESTINATION_RUNWAY ); const destinationRunwayDirection: number | undefined = destinationRunway ? Number(destinationRunway.name.replace(/\D+/, "") + "0") : undefined; // TODO: If no runways exist, check for gpl.departureRunway / gpl.destinationRunway // Set origin to runway if exists this.origin_icao = this.checkpoints[0].type === MissionCheckpoint.TYPE_ORIGIN ? this.checkpoints[0].name : ""; this.origin_dir = departureRunwayDirection ?? this.checkpoints[1].direction; this.origin_lon_lat = departureRunway?.lon_lat.getRelativeCoordinates(0.002, (departureRunwayDirection ?? 0) + 180) ?? this.checkpoints[0].lon_lat.clone(); // Set destination to runway if exists const checkpointDestination = this.findCheckPointByType(MissionCheckpoint.TYPE_DESTINATION) ?? this.checkpoints[this.checkpoints.length - 1]; this.destination_icao = checkpointDestination.type === MissionCheckpoint.TYPE_DESTINATION ? checkpointDestination.name : ""; this.destination_dir = destinationRunwayDirection ?? checkpointDestination.direction; this.destination_lon_lat = destinationRunway?.lon_lat.getRelativeCoordinates(0.5, destinationRunwayDirection ?? 0) ?? checkpointDestination.lon_lat.clone(); return this; } reverseWaypoints() { this.checkpoints = this.checkpoints.reverse(); if (this.checkpoints[0].type === MissionCheckpoint.TYPE_DESTINATION) { this.checkpoints[0].type = MissionCheckpoint.TYPE_ORIGIN; } if (this.checkpoints[1].type === MissionCheckpoint.TYPE_DESTINATION_RUNWAY) { this.checkpoints[1].type = MissionCheckpoint.TYPE_DEPARTURE_RUNWAY; } if (this.checkpoints[this.checkpoints.length - 2].type === MissionCheckpoint.TYPE_DEPARTURE_RUNWAY) { this.checkpoints[this.checkpoints.length - 2].type = MissionCheckpoint.TYPE_DESTINATION_RUNWAY; } if (this.checkpoints[this.checkpoints.length - 1].type === MissionCheckpoint.TYPE_ORIGIN) { this.checkpoints[this.checkpoints.length - 1].type = MissionCheckpoint.TYPE_DESTINATION; } const tmp_dir = this.origin_dir; const tmp_icao = this.origin_icao; const tmp_lon_lat = this.origin_lon_lat; this.origin_dir = this.destination_dir; this.origin_icao = this.destination_icao; this.origin_lon_lat = this.destination_lon_lat; this.destination_dir = tmp_dir; this.destination_icao = tmp_icao; this.destination_lon_lat = tmp_lon_lat; this.syncCruiseSpeed(); this.calculateCheckpoints(); const flight_category = this.conditions.getFlightCategory(this.origin_country !== "US"); this.setAutoTitleDescription(flight_category); } setAutoTitleDescription(flight_category: string = "") { if (flight_category === "") { flight_category = this.conditions.getFlightCategory(this.origin_country !== "US"); } if (this.title === "" || this.title === "Custom missions") { this.title = this.origin_icao !== this.destination_icao ? `From ${this.origin_icao} to ${this.destination_icao}` : `${this.origin_icao} local flight`; } if (this.description === "") { let localTime = this.getLocalDaytime(); localTime = (localTime.match(/^[aeiou]/) ? "An " : "A ") + localTime; const flight = this.origin_icao !== this.destination_icao ? `from ${this.origin_icao} to ${this.destination_icao}` : `at ${this.origin_icao}`; this.description = `${localTime} flight ${flight} under ${flight_category} conditions.`; this.description += ` Wind is ${this.conditions.wind_speed.toFixed()} kts from ${this.conditions.wind_direction.toFixed()}°.`; const navDescription = this.checkpoints .filter((c) => { return c.frequency > 0; }) .map((c) => { return `${c.name}: ${c.frequency_string}, DTK ${c.direction_magnetic.toFixed()}°`; }) .join("\n"); if (navDescription) { this.description += "\n\n" + navDescription; } } } syncCruiseAltitude() { this.checkpoints.forEach((c) => { if (c.type == MissionCheckpoint.TYPE_WAYPOINT) { c.lon_lat.altitude_m = this.cruise_altitude; } }); } syncCruiseSpeed() { let lastC: MissionCheckpoint | null = null; this.checkpoints.forEach((c) => { if ( c.type === MissionCheckpoint.TYPE_DEPARTURE_RUNWAY || (lastC && lastC.type === MissionCheckpoint.TYPE_DESTINATION_RUNWAY) ) { c.speed = 30; } else if (c.type !== MissionCheckpoint.TYPE_ORIGIN) { c.speed = this.cruise_speed; } c.ground_speed = c.speed; lastC = c; }); } calculateCheckpoints(changeHeight: null | MissionConditionsFlightRules = null) { let lastC: MissionCheckpoint | null = null; // Add directions this.checkpoints.forEach((c) => { if (lastC !== null) { c.setDirectionByCoordinates(lastC.lon_lat, changeHeight); } if ( c.type === MissionCheckpoint.TYPE_DEPARTURE_RUNWAY || (lastC && lastC.type === MissionCheckpoint.TYPE_DESTINATION_RUNWAY) ) { c.ground_speed = c.speed; c.heading = c.direction; } else { // Modify cruising speed by wind if (c.speed && c.direction >= 0 && this.conditions.wind_speed) { const windCorrection = this.conditions.getWindCorrection(c.direction_rad, c.speed); c.ground_speed = windCorrection.ground_speed; c.heading = windCorrection.heading; } else { c.ground_speed = c.speed; c.heading = c.direction; } } lastC = c; }); } set magnetic_declination(magneticDeclination: number | undefined) { this._magnetic_declination = magneticDeclination; this.origin_lon_lat.magnetic_declination = this.calculateMagneticDeclination( this.origin_lon_lat, magneticDeclination ); this.destination_lon_lat.magnetic_declination = this.calculateMagneticDeclination( this.destination_lon_lat, magneticDeclination ); this.checkpoints.forEach((cp) => { cp.lon_lat.magnetic_declination = this.calculateMagneticDeclination(cp.lon_lat, magneticDeclination); }); } get magnetic_declination(): number | undefined { return this._magnetic_declination; } /** * @see https://en.wikipedia.org/wiki/ICAO_airport_code * @param icaoAirportCode * @returns ISO 3166 only for Europe, North America and Australia */ protected icaoAirportToIsoCountry(icaoAirportCode: string): string { switch (icaoAirportCode.substring(0, 1)) { case "C": return "CA"; case "K": return "US"; case "Y": return "AU"; } switch (icaoAirportCode.substring(0, 2)) { case "BG": return "GL"; case "BI": return "IS"; case "BK": return "XK"; case "EB": return "BE"; case "ED": return "DE"; // Germany case "EE": return "EE"; // Estonia case "EF": return "FI"; case "EG": return "GB"; case "EH": return "NL"; case "EI": return "IE"; // Ireland case "EK": return "DK"; case "EL": return "LU"; case "EN": return "NO"; case "EP": return "PL"; case "ES": return "SE"; case "ET": return "DE"; // Germany case "EV": return "LT"; case "LT": return "LV"; // Latvia case "LA": return "AL"; case "LB": return "BG"; case "LC": return "CY"; case "LD": return "HR"; // Croatia case "LE": return "ES"; case "LF": return "FR"; case "LG": return "GR"; case "LH": return "HU"; case "LI": return "IT"; case "LJ": return "SI"; // Slovenia case "LK": return "CZ"; case "LL": return "IL"; case "LM": return "MT"; case "LN": return "MC"; // Monaco case "LO": return "AT"; case "LP": return "PT"; case "LQ": return "BA"; case "LR": return "RO"; case "LS": return "CH"; case "LT": return "TR"; case "LU": return "MD"; case "LV": return "PS"; // Palestine case "LW": return "MK"; case "LX": return "GI"; case "LY": return "RS"; case "LZ": return "SK"; // Slovakia } return ""; } /** * * @see https://en.wikipedia.org/wiki/List_of_aircraft_registration_prefixes * @param string ISO 3166 * @returns will only partially translate, falls back to 'N' */ protected isoCountryToCallsignPrefix(countryCode: string): string { switch (countryCode) { case "AL": return "ZA"; case "AT": return "OE"; case "AU": return "VH"; case "BE": return "OO"; case "BG": return "LZ"; case "CA": return "C"; case "CH": return "HB"; case "CY": return "5B"; case "CZ": return "OK"; case "DE": return "D"; case "DK": return "OY"; case "EE": return "ES"; case "ES": return "EC"; // EM case "FI": return "OH"; case "FR": return "F"; case "GB": return "G"; case "GI": return "VPG"; case "GR": return "SX"; case "HR": return "9A"; case "HU": return "HA"; case "IE": return "EI"; // EJ case "IS": return "TF"; case "IT": return "I"; case "LU": return "LX"; case "NL": return "PH"; case "NO": return "LN"; case "PL": return "SP"; // SN case "PT": return "CR"; // CS case "SE": return "SE"; default: return "N"; } } protected calculateMagneticDeclination(l: LonLat, magnetic_declination: number | undefined): number { if (magnetic_declination !== undefined) { return magnetic_declination; } // TODO: Get IPACS to disclose how to parse `world/magnetic.tmm` if (l.lon >= -123 && l.lon <= -119 && l.lat >= 37 && l.lat <= 40) { // Reno / San Francisco return 14; } // Formula for parts of Europe and Aerofly FS 4 return l.lon >= -10 && l.lon <= 26 && l.lat >= 45 ? (7 / 22) * l.lon - 3.4 : 0; } protected getLocalDaytime(): string { const localSolarTime = (this.conditions.time.time_hours + (this.origin_lon_lat.lon / 180) * 12 + 24) % 24; if (localSolarTime < 5 || localSolarTime >= 19) { return "night"; } if (localSolarTime < 8) { return "early morning"; } if (localSolarTime < 11) { return "morning"; } if (localSolarTime < 13) { return "noon"; } if (localSolarTime < 15) { return "afternoon"; } if (localSolarTime < 19) { return "late afternoon"; } return "day"; } findCheckPointByType(type: MissionCheckpointType): MissionCheckpoint | undefined { switch (type) { case MissionCheckpoint.TYPE_ORIGIN: return this.checkpoints[0]; case MissionCheckpoint.TYPE_DESTINATION: return this.checkpoints[this.checkpoints.length - 1]; case MissionCheckpoint.TYPE_DEPARTURE_RUNWAY: return this.checkpoints[1].type === type ? this.checkpoints[1] : undefined; case MissionCheckpoint.TYPE_DESTINATION_RUNWAY: return this.checkpoints[this.checkpoints.length - 2].type === type ? this.checkpoints[this.checkpoints.length - 2] : undefined; default: return this.checkpoints.find((cp) => { return cp.type === type; }); } } addCheckpointBefore(index: number, distance: number, altitudeChange = 0) { if (index < 1) { throw new RangeError("Cannot add waypoint at start of flight plan"); } const cpTo = this.checkpoints[index]; const cp = new MissionCheckpoint(); cp.lon_lat = cpTo.lon_lat.getRelativeCoordinates(distance, (cpTo.direction + 180) % 360); cp.lon_lat.altitude_ft += altitudeChange; cp.name = cpTo.name + "+" + distance.toFixed(); cp.speed = cpTo.speed; cp.ground_speed = cpTo.ground_speed; this.checkpoints.splice(index, 0, cp); } addCheckpointAfter(index: number, distance: number, altitudeChange = 0) { if (index > this.checkpoints.length - 2) { throw new RangeError("Cannot add waypoint at end of flight plan"); } const cpFrom = this.checkpoints[index]; const cpTo = this.checkpoints[index + 1]; const cp = new MissionCheckpoint(); cp.lon_lat = cpFrom.lon_lat.getRelativeCoordinates(distance, cpTo.direction); cp.lon_lat.altitude_ft += altitudeChange; cp.name = cpFrom.name + "+" + distance.toFixed(); cp.speed = cpTo.speed; cp.ground_speed = cpTo.ground_speed; this.checkpoints.splice(index + 1, 0, cp); this.calculateCheckpoints(); } toString(): string { if (this.no_guides) { // Create finish target plane 1m in front of aircraft origin position this.finish = new MissionCheckpoint(); this.finish.lon_lat = this.origin_lon_lat.getRelativeCoordinates(1 / Units.meterPerNauticalMile, this.origin_dir); } const finish = this.finish?.toStringTargetPlane("finish") ?? ""; let string = `\ // Exported by Aerofly Missionsgerät <[tmmission_definition][mission][] <[string8][title][${Quote.tmc(this.title)}]> <[string8][description][${Quote.tmc(this.description)}]> <[string8] [flight_setting] [${Quote.tmc(this.flight_setting)}]> <[string8u] [aircraft_name] [${Quote.tmc(this.aircraft_name)}]> //<[string8u][aircraft_livery] []> <[stringt8c] [aircraft_icao] [${Quote.tmc(this._aircraft_icao)}]> <[stringt8c] [callsign] [${Quote.tmc(this.callsign)}]> <[stringt8c] [origin_icao] [${Quote.tmc(this.origin_icao)}]> <[tmvector2d][origin_lon_lat] [${this.origin_lon_lat}]> <[float64] [origin_dir] [${this.origin_dir}]> <[stringt8c] [destination_icao] [${Quote.tmc(this.destination_icao)}]> <[tmvector2d][destination_lon_lat][${this.destination_lon_lat}]> <[float64] [destination_dir] [${this.destination_dir}]> //<[float64] [cruise_altitude] [${this.cruise_altitude}]> //<[float64] [cruise_speed] [${this.cruise_speed}]> ${this.conditions + finish}\ <[list_tmmission_checkpoint][checkpoints][] `; this.checkpoints.forEach((c, i) => { string += c.toString(i); }); string += `\ > > // ----------------------------------------------------------------------------- `; return string; } hydrate(json: Mission) { this._title = json._title ?? this._title; this._description = json._description ?? this._description; this.flight_setting = json.flight_setting ?? this.flight_setting; this._aircraft_name = json._aircraft_name ?? this._aircraft_name; this._aircraft_icao = json._aircraft_icao ?? this._aircraft_icao; this._magnetic_declination = json._magnetic_declination ?? this._magnetic_declination; this.callsign = json.callsign ?? this.callsign; this.origin_icao = json.origin_icao ?? this.origin_icao; this.origin_lon_lat.magnetic_declination = json.origin_lon_lat.magnetic_declination ?? this.origin_lon_lat.magnetic_declination; this.origin_lon_lat.lon = json.origin_lon_lat.lon ?? this.origin_lon_lat.lon; this.origin_lon_lat.lat = json.origin_lon_lat.lat ?? this.origin_lon_lat.lat; this.origin_lon_lat.altitude_m = json.origin_lon_lat.altitude_m ?? this.origin_lon_lat.altitude_m; this.origin_dir = json.origin_dir ?? this.origin_dir; this.destination_icao = json.destination_icao ?? this.destination_icao; this.destination_lon_lat.magnetic_declination = json.destination_lon_lat.magnetic_declination ?? this.destination_lon_lat.magnetic_declination; this.destination_lon_lat.lon = json.destination_lon_lat.lon ?? this.destination_lon_lat.lon; this.destination_lon_lat.lat = json.destination_lon_lat.lat ?? this.destination_lon_lat.lat; this.destination_lon_lat.altitude_m = json.destination_lon_lat.altitude_m ?? this.destination_lon_lat.altitude_m; this.destination_dir = json.destination_dir ?? this.destination_dir; this.cruise_speed = json.cruise_speed ?? this.cruise_speed; this.cruise_altitude = json.cruise_altitude ?? this.cruise_altitude; this.turn_time = json.turn_time ?? this.turn_time; this.no_guides = json.no_guides ?? this.no_guides; this.conditions.hydrate(json.conditions); this.finish = json.finish ?? this.finish; this.checkpoints = json.checkpoints.map((c) => { const cx = new MissionCheckpoint(); cx.hydrate(c); return cx; }); } } export class MissionFactory extends FileParser { create(configFileContent: string, mission: Mission): Mission { const tmmission_definition = this.getGroup(configFileContent, "tmmission_definition", 3); const tmmission_conditions = this.getGroup(configFileContent, "tmmission_conditions", 4); const list_tmmission_checkpoint = this.getGroup(configFileContent, "list_tmmission_checkpoint", 4); mission.title = this.getValue(tmmission_definition, "title"); mission.description = this.getValue(tmmission_definition, "description"); mission.flight_setting = this.convertFlightSetting(this.getValue(tmmission_definition, "flight_setting")); mission.aircraft_name = this.getValue(tmmission_definition, "aircraft_name"); mission.aircraft_icao = this.getValue(tmmission_definition, "aircraft_icao"); mission.callsign = this.getValue(tmmission_definition, "callsign"); mission.origin_icao = this.getValue(tmmission_definition, "origin_icao"); const origin_lon_lat = this.getNumberArray(tmmission_definition, "origin_lon_lat"); mission.origin_lon_lat.lon = origin_lon_lat[0]; mission.origin_lon_lat.lat = origin_lon_lat[1]; mission.origin_dir = this.getNumber(tmmission_definition, "origin_dir"); mission.destination_icao = this.getValue(tmmission_definition, "destination_icao"); const destination_lon_lat = this.getNumberArray(tmmission_definition, "destination_lon_lat"); mission.destination_lon_lat.lon = destination_lon_lat[0]; mission.destination_lon_lat.lat = destination_lon_lat[1]; mission.destination_dir = this.getNumber(tmmission_definition, "destination_dir"); mission.cruise_altitude = this.getNumber(tmmission_conditions, "cruise_altitude", mission.cruise_altitude); mission.cruise_speed = this.getNumber(tmmission_conditions, "cruise_speed", mission.cruise_speed); mission.conditions.time.time_year = this.getNumber(tmmission_conditions, "time_year"); mission.conditions.time.time_month = this.getNumber(tmmission_conditions, "time_month"); mission.conditions.time.time_day = this.getNumber(tmmission_conditions, "time_day"); mission.conditions.time.time_hours = this.getNumber(tmmission_conditions, "time_hours"); mission.conditions.wind_direction = this.getNumber(tmmission_conditions, "wind_direction"); mission.conditions.wind_speed = this.getNumber(tmmission_conditions, "wind_speed"); mission.conditions.wind_gusts = this.getNumber(tmmission_conditions, "wind_gusts"); mission.conditions.turbulence_strength = this.getNumber(tmmission_conditions, "turbulence_strength"); mission.conditions.thermal_strength = this.getNumber(tmmission_conditions, "thermal_strength"); mission.conditions.visibility = this.getNumber(tmmission_conditions, "visibility"); mission.conditions.cloud.cover = this.getNumber(tmmission_conditions, "cloud_cover"); mission.conditions.cloud.height = this.getNumber(tmmission_conditions, "cloud_base"); mission.finish = null; mission.checkpoints = list_tmmission_checkpoint .split("<[tmmission_checkpoint") .slice(1) .map((wp) => { const cp = new MissionCheckpoint(); cp.type = <MissionCheckpointType>this.getValue(wp, "type"); cp.name = this.getValue(wp, "name"); const lon_lat = this.getNumberArray(wp, "lon_lat"); cp.lon_lat.lon = lon_lat[0]; cp.lon_lat.lat = lon_lat[1]; cp.lon_lat.altitude_m = this.getNumber(wp, "altitude"); cp.direction = this.getNumber(wp, "direction"); cp.slope = this.getNumber(wp, "slope"); cp.length = this.getNumber(wp, "length"); cp.frequency = this.getNumber(wp, "frequency"); cp.speed = this.getNumber(wp, "speed", mission.cruise_speed); cp.flyOver = this.getValue(wp, "fly_over", "false") !== "false"; mission.cruise_altitude = Math.max(mission.cruise_altitude, cp.lon_lat.altitude_m); return cp; }); mission.calculateCheckpoints(); return mission; } protected convertFlightSetting(mainMcfFlightSetting: string): MissionFlightSetting { switch (mainMcfFlightSetting) { case "approach": return Mission.FLIGHT_SETTING_APPROACH; case "beforeStart": return Mission.FLIGHT_SETTING_BEFORE_START; case "coldAndDark": return Mission.FLIGHT_SETTING_COLD_AND_DARK; case "cruise": return Mission.FLIGHT_SETTING_CRUISE; case "landing": return Mission.FLIGHT_SETTING_LANDING; case "takeoff": return Mission.FLIGHT_SETTING_TAKEOFF; default: return Mission.FLIGHT_SETTING_TAXI; } } }