Analyzing Flight Simulator Data with The Wolfram Language

Diego Zviovich

Abstract

Microsoft Flight Simulator™  has been with us since the earliest 80’s. We will show how the Wolfram Language can be used to pull simulation data from this fabulous game, and how we can explore this wonderful virtual world using the powerful functions available in Mathematica.

Domains covered

WordCloud[Sort[{"Video Processing","Image Processing","Importing Data","Time Related Computation","Time Series Analysis","Wolfram Data Drop","MongoDB","Symbolic Model Discovery","Model Fitting & Regression","Descriptive Statistics","GeoComputation","Entities","Formulas"}],ImageSize->600]
AutoCollapse[]

History of the Microsoft Flight Simulator™

player="";TimelinePlot[aHistory,ImageSize->1024]
Dynamic[player]
AutoCollapse[]

Accessing the Flight Simulator Data

We used an existing python interface for the flight simulator 2020 that makes use of the simconnect.dll from Prepar3D® .

Code Example

fsIP="192.168.4.73"; (*IP Address of Machine running FS Sim*)
getVariable[var_,IP_:fsIP]:=<|var->Import["http://"<>IP<>":5000/datapoint/"<>var<>"/get"]|>
SetAttributes[getVariable,Listable]
getVariableList[lst_List,IP_:fsIP]:=Merge[getVariable[lst,IP],Last]

(*getVariable["ENG_OIL_QUANTITY:1"]*)

Exposed Simulator Variables

simVar["Source"]
Manipulate[simVar[categories],{categories,Keys[simVar]}]
AutoCollapse[]

Capturing Flight Data

Start the Microsoft Flight Simulator 2020, and then execute the Python Script glass_server.py, located in the MSFS2020-CC directory.

vars={"ABSOLUTE_TIME","AILERON_POSITION","AIRSPEED_INDICATED","AIRSPEED_TRUE","AMBIENT_DENSITY","AMBIENT_PRESSURE","AMBIENT_TEMPERATURE","AMBIENT_WIND_DIRECTION","AMBIENT_WIND_VELOCITY","AMBIENT_WIND_X","AMBIENT_WIND_Y","AMBIENT_WIND_Z","ATTITUDE_INDICATOR_BANK_DEGREES","ATTITUDE_INDICATOR_PITCH_DEGREES","BAROMETER_PRESSURE","ELEVATOR_POSITION","FLAPS_HANDLE_PERCENT","FUEL_TOTAL_QUANTITY_WEIGHT","GENERAL_ENG_RPM:1","LOCAL_TIME","PLANE_ALT_ABOVE_GROUND","PLANE_ALTITUDE","PLANE_HEADING_DEGREES_TRUE","PLANE_LATITUDE","PLANE_LONGITUDE","RECIP_ENG_CYLINDER_HEAD_TEMPERATURE:1","RECIP_ENG_TURBINE_INLET_TEMPERATURE:1","RECIP_MIXTURE_RATIO:1","RELATIVE_WIND_VELOCITY_BODY_X","RELATIVE_WIND_VELOCITY_BODY_Y","RELATIVE_WIND_VELOCITY_BODY_Z","RUDDER_PEDAL_POSITION","RUDDER_POSITION","STALL_WARNING","VERTICAL_SPEED","YOKE_X_POSITION","YOKE_Y_POSITION"};

Length@vars

Logging the data with Databin

(*Do[DatabinAdd[simBin,getVariableList[vars]];,10^5]*)

Reading the data from Databin

simBin=Databin["XWS9TXap"];aDBinTimeSeries=TimeSeries[simBin];

View the trend of the different variables along the flight.

Grid[Partition[DateListPlot[#2,PlotLabel->Style[#1,Small]]&@@@Normal[KeyDrop[aDBinTimeSeries,{"LOCAL_TIME","STALL_WARNING","ABSOLUTE_TIME"}]],UpTo[6]]]
AutoCollapse[]

Flight was captured on video. Let’s bring it in so we can synchronize it with the variables the trend of the different variables along the flight .

vid=Video[FileNameJoin[{NotebookDirectory[],"Videos","Databin 2021-08-22_09-30-22.mp4"}]]
vidStream=VideoStream[vid];AutoCollapse[]

Get the Clock from the video.

imgClock=ImageResize[ImageTrim[vidStream["CurrentFrame"],{{1010,185},{890,290}}],500]
AutoCollapse[]

Obtain the local time records from the databin.

timeStamps=DatePlus[TimeObject[{0,0,0},TimeZone->None],Quantity[#,"Seconds"]]&/@(aDBinTimeSeries["LOCAL_TIME"]["Values"]);
timeStamps=timeStamps[[7;;]];Labeled[imgClock,vidStart=First@timeStamps,Bottom]
AutoCollapse[]

aFSimVariablesBin=Map[TimeSeries[Transpose[{timeStamps,((#["Values"])[[7;;]])}]]&,aDBinTimeSeries];Manipulate[With[{ts=DatePlus[vidStart,Quantity[secs,"Seconds"]]},vidStream["Position"]=secs;GraphicsRow[{vidStream["CurrentFrame"],Rasterize@Show[ListLinePlot3D[Transpose[#["Values"]&/@Values[KeyTake[aFSimVariablesBin,{"PLANE_LONGITUDE","PLANE_LATITUDE","PLANE_ALTITUDE"}]]],ImageSize->1024],Graphics3D[{Red,PointSize[0.05],Point[{aFSimVariablesBin["PLANE_LONGITUDE"][ts],aFSimVariablesBin["PLANE_LATITUDE"][ts],aFSimVariablesBin["PLANE_ALTITUDE"][ts]}]}]]},ImageSize->1024]],{secs,10,QuantityMagnitude[vidStream["Duration"],"Seconds"]-10,1},SynchronousUpdating->False]
AutoCollapse[]

Using the Video Processing functionality we can generate a new video combining the captured video with data visualization.

(*aFSimVariablesBinNew=Map[TimeSeries[Transpose[{timeStamps[[263;;]],((#["Values"])[[-47;;]])}]]&,aDBinTimeSeries];chart[secs_]:=ImageResize[With[{ts=DatePlus[vidStart,Quantity[secs,"Seconds"]]},Show[ListLinePlot3D[Transpose[#["Values"]&/@Values[KeyTake[aFSimVariablesBinNew,{"PLANE_LONGITUDE","PLANE_LATITUDE","PLANE_ALTITUDE"}]]],ImageSize->400,ViewPoint->{0.04082768791350663,-3.322339029352339,0.6406219430692268},Filling->Bottom,PlotTheme->"Detailed"],Graphics3D[{Red,PointSize[0.05],Point[{aFSimVariablesBinNew["PLANE_LONGITUDE"][ts],aFSimVariablesBinNew["PLANE_LATITUDE"][ts],aFSimVariablesBinNew["PLANE_ALTITUDE"][ts]}]}]]],300];j=ParallelTable[chart[n],{n,1000,1100,1/30}];Export[FileNameJoin[{NotebookDirectory[],"Videos","listplot3d.mp4"}],j,FrameRate->30];vidChart=Video[FileNameJoin[{NotebookDirectory[],"Videos","listplot3d.mp4"}]];
vidTrim=VideoTrim[vid,{1000,1100}];finalVid=Parallelize[VideoFrameMap[ImageCompose[#[[1]],#[[2]],{1755,950}]&,{vidTrim,vidChart}]];
Export[FileNameJoin[{NotebookDirectory[],"Videos","landingVid.mp4"}],finalVid];*)

landing=Video[FileNameJoin[{NotebookDirectory[],"Videos","landingVid.mp4"}]]

Related video from the flight can be found here:

Hyperlink["https://youtu.be/nuelKm0coi4"]

Glide Path Analysis

Let’s look at the Cessna 152 manual. The table indicates that the plane will glide 1.6 nautical miles per 1000 ft lost in glide.

i=ImageTrim[Import[FileNameJoin[{NotebookDirectory[],"cessna manual.pdf"}],{"PageImages",33}],]
AutoCollapse[]

Find glide path

For the glide we reduced to zero the fuel going into the carburator to make the propeller windmill as instructed in the chart above. The TimeSeries shows the exact moment when the fuel was cut off.

GraphicsRow[DateListPlot[#2,PlotLabel->#1]&@@@Normal[KeyTake[aDBinTimeSeries,{"RECIP_MIXTURE_RATIO:1","GENERAL_ENG_RPM:1","PLANE_ALTITUDE"}]],ImageSize->1024]
AutoCollapse[]

Identify the variable  snapshots when the plane was starved of fuel.

pos=Position[aDBinTimeSeries["RECIP_MIXTURE_RATIO:1"]["Values"],0.];
tsGlide=TimeSeries[Extract[aDBinTimeSeries["PLANE_ALTITUDE"]["Path"],pos]];fg=DateListPlot[tsGlide,];
AutoCollapse[]

Show the glide path under analysis:

distanceCovered=GeoDistance/@Partition[path=(GeoPosition/@Extract[Transpose[{aDBinTimeSeries["PLANE_LATITUDE"]["Values"],aDBinTimeSeries["PLANE_LONGITUDE"]["Values"]}],pos]),2,1];
glideRatio=TimeSeries[Most@tsGlide["Times"]->distanceCovered/Quantity[Differences[tsGlide["Values"]],"Feet"]];
GraphicsRow[{fg,GeoGraphics[{Red,Arrow[path]}]},ImageSize->1024]
AutoCollapse[]

Calculate the distance traveled during the glide.

heightAboveTerrain=Most[Quantity[Reverse[tsGlide["Values"]]-tsGlide["LastValue"],"Feet"]];glideDistance=UnitConvert[Accumulate[Reverse[distanceCovered]]-Last[distanceCovered],"NauticalMiles"];
Last[glideDistance]
AutoCollapse[]

Fit Linear Model to glide path.

lm=LinearModelFit[QuantityMagnitude@Transpose[{glideDistance,heightAboveTerrain}],x,x,IncludeConstantBasis->False]

Determine nautical miles travel with a drop of 1000 feet.

1000/First@lm["BestFitParameters"]

%/1.6

GraphicsRow[{i,Show[Plot[lm[x],{x,0,2.5},],ListPlot[Transpose[{glideDistance,heightAboveTerrain}],],]},]
AutoCollapse[]

Logging the data with MongoDB

(*<<"MongoLink`"*)
(*localLink="mongodb://localhost:27017"*)
(*RunProcess[$SystemShell,All,"/usr/local/Homebrew/bin/brew services start mongodb-community@5.0"]*)
(*client=MongoConnect[localLink]*)
(*db=MongoGetDatabase[client,"FlightSimDB"]*)
(*collection=MongoGetCollection[db,"FSLog2108221100"]*)
(*Do[MongoCollectionInsert[collection,getVariableList[vars]];,10^5]*)
(*RunProcess[$SystemShell,All,"/usr/local/Homebrew/bin/brew services stop mongodb-community@5.0"]*)

Reading the data with MongoDB

<<"MongoLink`"

client=MongoConnect[];MongoGetDatabaseNames[client];mongoDB=MongoGetDatabase[client,"FlightSimDB"];MongoGetCollectionNames[mongoDB]
AutoCollapse[]

coll=MongoGetCollection[mongoDB,"FSLog2108221100"];curs=MongoCollectionFind[coll];dsVariables=Dataset[ReadList[curs],MaxItems->{10,6}]
AutoCollapse[]

Data has been made available to download here :

Hyperlink["https://raw.githubusercontent.com/dzviovich/WTC2021/main/FSLog2108221100.json"]

Exploring the Dataset from the flight

dsPath=dsVariables[All,<|"Time"->#"LOCAL_TIME"/3600.,"Long"->#"PLANE_LONGITUDE","Lat"->#"PLANE_LATITUDE","Alt"->#"PLANE_ALTITUDE"|>&]

pathPoints=dsVariables[All,GeoPosition[{#"PLANE_LATITUDE",#"PLANE_LONGITUDE"}]&]//Normal;texture=GeoImage[GeoBoundingBox[pathPoints],ImageSize->1024]
AutoCollapse[]

elev=GeoElevationData[GeoBoundingBox[pathPoints]];terrain=ListPlot3D[Reverse[elev],PlotStyle->Texture[texture],ImageSize->1400,PlotRange->{Min[elev],Max[elev]1.5},MeshFunctions->{#3&},Mesh->10,ViewPoint->{-2.9140036796231805`,-1.4014396925230412`,0.8195042651805944}];dims=Reverse@Dimensions[elev]-1;points=Normal[Values[dsPath]][[All,2;;]];flightPath=ListLinePlot3D[rescaled=({Rescale[#1,MinMax[points[[All,1]]]]dims[[1]]+1,Rescale[#2,MinMax[points[[All,2]]]]dims[[2]]+1,#3}&@@@points),PlotStyle->Orange];
Rasterize[Show[terrain,flightPath]]
AutoCollapse[]

Related video from the flight can be found here.

Hyperlink["https://youtu.be/j_TOLHhu-YM"]

Does the simulator environment obey the laws of nature?

FormulaLookup["Law of ideal gas density"]

FormulaData[First@%]

⇒(P/(ρ T))= K

DateListPlot[tsGasConstant=TimeSeries[Flatten@Normal@Normal[dsVariables[All,<|DatePlus[TimeObject[{0,0,0}],Quantity[#"LOCAL_TIME","Seconds"]]->UnitConvert[Quantity[#"AMBIENT_PRESSURE","InchesOfMercury"],"Pascals"]/(UnitConvert[Quantity[#"AMBIENT_DENSITY","SlugsMass"/"Feet"^3],"Kilograms"/"Meters"^3] UnitConvert[Quantity[#"AMBIENT_TEMPERATURE","DegreesCelsius"],"Kelvins"])|>&]]],]
AutoCollapse[]

Mean[tsGasConstant]

UnitConvert[EntityInstance[(Entity["dry air", Entity["Chemical","DryAir"], "Entity[\"Chemical\", \"DryAir\"]", "chemical"]), (Quantity[1, "mol", mole, "Moles"])]["MolarMass"],"Kilograms"/"Moles"]*Mean[tsGasConstant]

8.31335/8.314

Airspeed indicator vs True speed

Credit:Er.Naser.F.AB.Elmajdub,Dr.A.K.Bharadwaj - Important Pitot Static System in Aircraft Control System

speedStudy=Normal[dsVariables[All,{#"PLANE_ALTITUDE",#"AIRSPEED_TRUE"/(0.00001+#"AIRSPEED_INDICATED")}&]]/.Null->0.;ListPlot[speedStudy[[All,1]],]
AutoCollapse[]

Focus the study on the climb.

ListPlot[speedStudy[[20;;165,1]],]
AutoCollapse[]

ListPlot[speedStudy[[20;;165]],]
AutoCollapse[]

lm=LinearModelFit[speedStudy[[20;;165]],x,x]
lm["BestFitParameters"]

clean=DeleteCases[speedStudy[[20;;165]],{a_/;And[a>7100.,a<7490.],_},Infinity];
ListPlot[{speedStudy[[20;;165]],clean},]
AutoCollapse[]

lm2=LinearModelFit[clean,x,x]

On average, true airspeed increases about 2% per 1,000’ of increase in altitude, but the actual change depends on temperature and pressure.

lm2["BestFitParameters"][[2]]*1000

Show[ListPlot[clean,PlotRange->{1.05,1.15},],Plot[lm2[x],{x,5000,9000}]]
AutoCollapse[]

Analyzing Aerodynamics

Credit: Wikipedia

Column[{TraditionalForm[L==1/2 ρ v^2 S (Subscript[C, l])],Spacer[10],"Where",Spacer[10],Grid[{{"L","Lift Force"},{"ρ","Air Density"},{"v","True Air Speed"},{"S","Projected Wing Area"},{"(Subscript[C, l])","Lift Coefficient"}},Alignment->{Left,Right},Frame->All]}] 
AutoCollapse[]

collPower=MongoGetCollection[mongoDB,"FSLog2108241715"];
cursPower=MongoCollectionFind[collPower];dsPower=Dataset[ReadList[cursPower],MaxItems->{10,20}]
AutoCollapse[]

GeoGraphics[{Red,Thick,Line[GeoPosition[DeleteCases[Normal[dsPower[All,{#"PLANE_LATITUDE",#"PLANE_LONGITUDE"}&]],Alternatives[{_,Null},{Null,_}]]]]},GeoBackground->"VectorMinimal",ImageSize->1024]
AutoCollapse[]

powerStudy=<||>;
powerStudy["RPM"]=Rest@Normal@dsPower[All,"GENERAL_ENG_RPM:1"][[;;-4]];
powerStudy["ALTITUDE"]=Rest@Normal@dsPower[All,"PLANE_ALTITUDE"][[;;-4]];
powerStudy["AIRSPEED"]=Rest@Normal@dsPower[All,"AIRSPEED_TRUE"][[;;-4]];
powerStudy["CLIMBRATE"]=Rest@Normal@dsPower[All,"VERTICAL_SPEED"][[;;-4]];posRPM=Position[powerStudy["RPM"],a_/;a>2425];
posAlt=Position[powerStudy["ALTITUDE"],a_/;a>3000];""
AutoCollapse[]

powerStudy["POINTSOFINTEREST"]=Extract[Transpose[{powerStudy["ALTITUDE"],powerStudy["CLIMBRATE"]}],Intersection[posRPM,posAlt]];
lp=ListPlot[powerStudy["POINTSOFINTEREST"],]
AutoCollapse[]

lmROC=LinearModelFit[powerStudy["POINTSOFINTEREST"],x,x]

Row[{ListPlot[lmROC["FitResiduals"],],QuantilePlot[lmROC["StandardizedResiduals"],]}]
AutoCollapse[]

mpROC[x_]=lmROC["MeanPredictionBands"];Show[lp,Plot[{lmROC[x],mpROC[x]},{x,3000,6600}]]
AutoCollapse[]

maxROCImg=ImageTrim[Import[FileNameJoin[{NotebookDirectory[],"cessna manual.pdf"}],{"PageImages",70}],]
AutoCollapse[]

tabPoints={{3000,590},{4000,550},{5000,505},{6000,465}};Show[ListPlot[{powerStudy["POINTSOFINTEREST"],tabPoints},],Plot[{lmROC[x],mpROC[x]},{x,3000,6600}],ImageSize->860]
AutoCollapse[]

Angle of Attack vs RPM

Credit: Skyrary

col=MongoGetCollection[mongoDB,"FSLog2109062258"];
curs=MongoCollectionFind[col];
dsLift=Dataset[ReadList[curs],MaxItems->{10,6}]
AutoCollapse[]

Histogram[Normal[dsLift[All,"VERTICAL_SPEED"]],]
AutoCollapse[]

Only interested in data points where the absolute value of the vertical speed is less than 60 fpm.

pos=Flatten[Position[Normal@dsLift[All,"VERTICAL_SPEED"],a_/;a<60]];
aLiftStudy=<||>;
aLiftStudy["Speed"]=UnitConvert[QuantityArray[Normal@dsLift[pos,"AIRSPEED_TRUE"],"Knots"],"Meters"/"Seconds"];

aLiftStudy["AOA"]=UnitConvert[Quantity[180,"AngularDegrees"]-QuantityArray[Normal@dsLift[pos,"ANGLE_OF_ATTACK_INDICATOR"],"Radians"],"AngularDegrees"];
aLiftStudy["VerticalSpeed"]=Normal@dsLift[pos,"VERTICAL_SPEED"];
aLiftStudy["Density"]=UnitConvert[QuantityArray[Normal@dsLift[pos,"AMBIENT_DENSITY"],"SlugsMass"/"Feet"^3],"Kilograms"/"Meters"^3];
aLiftStudy["Weight"]=UnitConvert[QuantityArray[Normal@dsLift[pos,"TOTAL_WEIGHT"] ,"Pounds"],"Kilograms"];
aLiftStudy["RPM"]=Normal@dsLift[pos,"GENERAL_ENG_RPM:1"];""
AutoCollapse[]

ListPlot[lstRPMAOA=Transpose[{aLiftStudy["RPM"],QuantityMagnitude[aLiftStudy["AOA"],"AngularDegrees"]}],]
AutoCollapse[]

posOutliers=Flatten@Position[lstRPMAOA,{a_/;a>1725,b_/;b>12}];lstRPMAOA=DeleteCases[Normal[lstRPMAOA],{a_/;a>1725,b_/;b>12}];
ListPlot[lstRPMAOA,]
AutoCollapse[]

ff=FindFormula[lstRPMAOA]
Show[ListPlot[lstRPMAOA,],Plot[ff[x],{x,1700,2500},PlotStyle->Red]]
AutoCollapse[]

Lift Coefficient Analysis

Credit: NASA Glen Research Center

pos=Complement[pos,posOutliers];
aLiftStudy=<||>;aLiftStudy["Speed"]=UnitConvert[QuantityArray[Normal@dsLift[pos,"AIRSPEED_TRUE"],"Knots"],"Meters"/"Seconds"];
aLiftStudy["RPM"]=QuantityArray[Normal@dsLift[pos,"GENERAL_ENG_RPM:1"],"Revolutions"/"Minutes"];
aLiftStudy["AOA"]=UnitConvert[Quantity[180,"AngularDegrees"]-QuantityArray[Normal@dsLift[pos,"ANGLE_OF_ATTACK_INDICATOR"],"Radians"],"AngularDegrees"];
aLiftStudy["VerticalSpeed"]=QuantityArray[Normal@dsLift[pos,"VERTICAL_SPEED"],"Feet"/"Minutes"];
aLiftStudy["Mass"]=UnitConvert[QuantityArray[Normal@dsLift[pos,"TOTAL_WEIGHT"] ,"Pounds"],"Kilograms"];
aLiftStudy["Density"]=UnitConvert[QuantityArray[Normal@dsLift[pos,"AMBIENT_DENSITY"],"SlugsMass"/"Feet"^3],"Kilograms"/"Meters"^3];
aLiftStudy["Surface"]=UnitConvert[LinguisticAssistant["WingArea"],"Meters"^2];
g=Quantity[9.81,"Meters"/"Seconds"^2];
aLiftStudy["CL"]=aLiftStudy["Mass"] g / ( 0.5aLiftStudy["Density"]aLiftStudy["Surface"]aLiftStudy["Speed"]^2(*Cos[aLiftStudy["AOA"]]^2 *));
ListPlot[Transpose[{aLiftStudy["AOA"],aLiftStudy["CL"]}],]
AutoCollapse[]

lm=LinearModelFit[Transpose[{QuantityMagnitude[aLiftStudy["AOA"],"AngularDegrees"],aLiftStudy["CL"]}],x,x]

Show[ListPlot[Transpose[{aLiftStudy["AOA"],aLiftStudy["CL"]}],],Plot[lm[x],{x,0,13},PlotStyle->Red]]
AutoCollapse[]

Hyperlink["https://ukdiss.com/examples/effects-icing-cessna-152-aircraft.php#_Toc480352859"]

From  All Answers Ltd. (November 2018). Effects of Severe Icing Conditions on Aerodynamic Performance of Cessna 152 Aircraft. Retrieved from https://ukdiss.com/examples/effects-icing-cessna-152-aircraft.php?vref=1

img=ImageResize[Import["https://images.ukdissertations.com/12/0009792.050.jpg"],860]

bWing=First@Last@DominantColors[img,Automatic,{"CoverageImage","Color"}]

bReadings=DeleteCases[ComponentMeasurements[bWing,"Centroid",#Area>60&][[All,2]],{_,a_/;a<45}]

bReadingsNew=Transpose[{Rescale[bReadings[[All,1]],{228.87,824.27},{0,20}],Rescale[bReadings[[All,2]],{294.644,500.217},{0,0.8}]}]

Show[ListPlot[{Transpose[{aLiftStudy["AOA"],aLiftStudy["CL"]}],bReadingsNew},],Plot[lm[x],{x,0,13},PlotStyle->Red]]
AutoCollapse[]

References

History of the Microsoft Flight Simulator

Bruce Artwicks thesis: A versatile computer-generated dynamic flight display

Prepare3D Learning Center

Python SimConnect 0.4.24

Cessna 152 Pilot Operating Handbook

Sample Data Log in Github

Effects of Severe Icing Conditions on Aerodynamic Performance of Cessna 152 Aircraft.

Thanks to

Anton Antonov

Roberto Peon

Washington DC Area Mathematica Special Interest Group (SIG)

Initialization

dir=NotebookDirectory[];
aMedia=<|"FS1"->VideoStream[FileNameJoin[{dir,"Videos","subLOGIC Flight Simulator 1 for Apple II - the first ever!!!.mp4"}]],

"95"->VideoStream[FileNameJoin[{dir,"Videos","fs95New.mp4"}]],"1.0"->Import[FileNameJoin[{dir,"Images","fs1.png"}]],
"2020"->VideoStream[FileNameJoin[{dir,"Videos","fs20Small.mp4"}]]|>;
aHistory=Association[Button["FS1", player = setPlayer[aMedia["FS1"]], 
    Appearance -> Automatic, Evaluator -> Automatic, 
    Method -> "Preemptive"] -> DateObject[{1979}, "Year", "Gregorian", 
    -4.], Button["1.0", player = setPlayer[aMedia["1.0"]], 
    Appearance -> Automatic, Evaluator -> Automatic, 
    Method -> "Preemptive"] -> DateObject[{1982}, "Year", "Gregorian", 
    -4.], "2.0" -> DateObject[{1984}, "Year", "Gregorian", -4.], 
  "for Macintosh" -> DateObject[{1986}, "Year", "Gregorian", -4.], 
  "3.0" -> DateObject[{1988}, "Year", "Gregorian", -4.], 
  "4.0" -> DateObject[{1989}, "Year", "Gregorian", -4.], 
  "5.0" -> DateObject[{1993}, "Year", "Gregorian", -4.], 
  "5.1" -> DateObject[{1995}, "Year", "Gregorian", -4.], 
  Button["for Win95", player = setPlayer[aMedia["95"]], 
    Appearance -> Automatic, Evaluator -> Automatic, 
    Method -> "Preemptive"] -> DateObject[{1996}, "Year", "Gregorian", 
    -4.], "98" -> DateObject[{1997}, "Year", "Gregorian", -4.], 
  "2000" -> DateObject[{1999}, "Year", "Gregorian", -4.], 
  "2002" -> DateObject[{2001}, "Year", "Gregorian", -4.], 
  "2004: A century of flight" -> DateObject[{2003}, "Year", 
    "Gregorian", -4.], "X" -> DateObject[{2006}, "Year", "Gregorian", 
    -4.], "X: Steam Edition" -> DateObject[{2014}, "Year", "Gregorian", 
    -4.], Button["2020", player = setPlayer[aMedia["2020"]], 
    Appearance -> Automatic, Evaluator -> Automatic, 
    Method -> "Preemptive"] -> DateObject[{2020}, "Year", "Gregorian", -4.]];
    
    simVar=;
    simVar=Map[Dataset,simVar];
    simVar["Source"]=Hyperlink["http://www.prepar3d.com/SDKv2/LearningCenter/utilities/variables/simulation_variables.html"];
    i=LinguisticAssistant["Image"];
    Get[FileNameJoin[{dir,"OutputDeleteUtility.wl"}]];
    DeleteOutputPalette[];

(*Functions*)
setPlayer[obj_VideoStream]:=Quiet@DynamicModule[{stream=obj},
Manipulate[stream["Position"]=pos;
stream["CurrentFrame"],
{pos,0,QuantityMagnitude[stream["Duration"],"Seconds"],
ControlPlacement->Bottom}]]
setPlayer[obj_String]:=obj
setPlayer[obj_Image]:=ImageResize[obj,600]
AutoCollapse[] := (
  If[$FrontEnd =!= $Failed, 
   SelectionMove[EvaluationNotebook[], All, GeneratedCell];
   FrontEndTokenExecute["SelectionCloseUnselectedCells"]])
   
   RunProcess[$SystemShell,All,"/usr/local/Homebrew/bin/brew services start mongodb-community@5.0"]