import type { AeroflyFlight } from "@fboes/aerofly-custom-missions";
import { AeroflyFlightToStringConverter } from "./AeroflyFlightToStringConverter.js";
import {
dateToString,
getClouds,
getFlightplanDestinationName,
getFlightplanOriginCode,
getFlightplanOriginName,
getHourString,
getMinuteString,
getSunPositionName,
getTemperature,
getVisibility,
getWind,
} from "../../formatter/AeroflyFlightFormatter.js";
import { markdownTable } from "../../formatter/markdownTable.js";
import { getAeroflyAircraft, getAeroflyLivery } from "../../services/getAeroflyAircraft.js";
import { RoutePlanService } from "../../services/RoutePlanService.js";
import { getFlightCategory, getIcaoFlightCategory, getLocalTimeAndDate } from "../../util/AeroflyFlightHelper.js";
import { AeroflyFlightToMetarConverter } from "./AeroflyFlightToMetarConverter.js";
import { SkyVectorUrl } from "../../data/SkyVectorUrl.js";
import { APPLICATION_INFORMATION } from "../../services/getApplicationInformation.js";
export class AeroflyFlightToMarkdownConverter extends AeroflyFlightToStringConverter {
static readonly fileName = "Markdown Text File";
static readonly fileExtension = "md";
convert(flightplan: AeroflyFlight): string {
return `\
# ${this.getFlightplanTitle(flightplan)}
${this.getMissionBriefing(flightplan)}
${this.getAircraftSummary(flightplan)}
${this.getTimeSummary(flightplan)}
${this.getWeatherSummary(flightplan)}
${this.getFlightSummary(flightplan)}
`;
}
private getAircraftSummary(flightplan: AeroflyFlight) {
const currentAircraft = getAeroflyAircraft(flightplan.aircraft.name);
if (!currentAircraft) {
return "";
}
const currentLivery = getAeroflyLivery(currentAircraft, flightplan.aircraft.paintscheme);
// TODO: Fuel and payload are not yet accessible via the AeroflyFlight API. For now, we just set them to false.
const fuel = false; // flightplan.fuelLoadSetting.fuelMass || flightplan.fuelLoadSetting.payloadMass;
return `\
## Aircraft
${markdownTable([
["Aircraft", "Livery", "Cruise speed", "Cruise altitude"],
["---", "---", "---:", "---:", "---:", "---:"],
[
currentAircraft.nameFull,
currentLivery?.name ?? "Default",
currentAircraft.cruiseSpeedKts ? this.numericOutput(currentAircraft.cruiseSpeedKts, " kts") : "not set",
flightplan.navigation.cruiseAltitude_ft
? this.numericOutput(flightplan.navigation.cruiseAltitude_ft, " ft")
: "not set",
fuel ? this.numericOutput(flightplan.fuelLoadSetting.fuelMass, " kg") : "",
fuel ? this.numericOutput(flightplan.fuelLoadSetting.payloadMass, " kg") : "",
],
])}
`;
}
private getTimeSummary(flightplan: AeroflyFlight) {
return `\
## Departure time & date
${markdownTable([
["Timezone", "Date", "Time", "Remark"],
["---", "--:", "---:", "------"],
["UTC", ...dateToString(flightplan.timeUtc.time).split(" "), ""],
[
getFlightplanOriginCode(flightplan) + "¹",
...dateToString(getLocalTimeAndDate(flightplan)).split(" "),
getSunPositionName(flightplan),
],
])}
- ¹) Nautical time zone
`;
}
private getWeatherSummary(flightplan: AeroflyFlight) {
const m = new AeroflyFlightToMetarConverter();
return `\
## Weather
> ${m.convert(flightplan)}
${markdownTable([
["Wind", "Clouds", "Visibility", "Temperature", "Flight Categegory"],
["---", "--:", "--:", "--:", "---"],
[
getWind(flightplan),
getClouds(flightplan, "
"),
getVisibility(flightplan, "
"),
getTemperature(flightplan),
`${getIcaoFlightCategory(flightplan)} (ICAO)
${getFlightCategory(flightplan)} (US)`,
],
])}
`;
}
private getFlightSummary(flightplan: AeroflyFlight) {
const skyvector = new SkyVectorUrl(flightplan);
const route = new RoutePlanService(flightplan);
const routeLegs = route.getRouteLegs();
const routeTotalTime = routeLegs.at(-1)?.estimatedTimeEnrouteTotal_min ?? 0;
const timeFunction: (minutes: number) => string = routeTotalTime < 60 ? getMinuteString : getHourString;
return `\
## Flight details
${markdownTable([
["From", "To", "Freq²", "Altitude²", "Track", "HDG", "GS", "Dist", "ETE³", "ETO³"],
["---", "---", "---:", "---:", "---:", "---:", "---:", "---:", "---:", "---:"],
...routeLegs.map((l) => [
l.from,
l.to,
l.frequency_mhz
? this.numericOutput(
l.frequency_mhz > 1 ? l.frequency_mhz : l.frequency_mhz / 1000,
l.frequency_mhz > 1 ? " MHz" : " kHZ",
l.frequency_mhz > 1 ? 1 : 0,
)
: "",
l.altitude_ft ? this.numericOutput(l.altitude_ft, " ft") : "",
this.numericOutput(l.track_deg, "°"),
this.numericOutput(l.heading_deg, "°"),
this.numericOutput(l.groundSpeed_kts, " kts"),
this.numericOutput(l.distance_nm, " NM", 1),
timeFunction(l.estimatedTimeEnroute_min),
timeFunction(l.estimatedTimeEnrouteTotal_min),
]),
])}
- [Skyvector: ${getFlightplanOriginName(flightplan)}](${skyvector.getOriginURL().toString()})
- [Skyvector: ${getFlightplanDestinationName(flightplan)}](${skyvector.getDestinationURL().toString()})
- [SkyVector: ${this.getFlightplanTitle(flightplan)}](${skyvector.getRouteURL().toString()})
- ²) Value for "To" waypoint
- ³) Duration in ${timeFunction === getMinuteString ? "mm:ss" : "hh:mm"}
`;
}
private numericOutput(value: number, unit: string = "", minimumFractionDigits = 0): string {
return (
Intl.NumberFormat(APPLICATION_INFORMATION.locale, {
minimumFractionDigits,
maximumFractionDigits: minimumFractionDigits,
}).format(value) + unit
);
}
}