From 21b024e312e47f505ce5b998bec870130714186b Mon Sep 17 00:00:00 2001 From: Oren Geva Date: Mon, 5 Jun 2023 14:20:33 +0300 Subject: [PATCH] Logbook sidebar (#2) * Logbook pages * Searchbox * Allow 500 ms before querying bd for search strings * Textbox * Add icon to tray * Minimize * Error handling * Prevent multiple instances of FSTrAk * Added show path and flight details button to details view * Charts * Read aircraft params from files * Scoreboard * Pin colors * Resolve airport names * Remove landing bounces * City names * Manufacturer Capitalization * Selection design --- FSTRaK.sln | 27 +- FSTRaK/App.xaml | 1 + FSTRaK/App.xaml.cs | 48 +- FSTRaK/DataTypes/SimConnectDataTypes.cs | 25 +- FSTRaK/FSTRaK.csproj | 67 +- FSTRaK/FSTrAk.csproj | 58 +- FSTRaK/Models/AirportResolver.cs | 49 + FSTRaK/Models/Entity/Aircraft.cs | 20 +- FSTRaK/Models/Entity/Airport.cs | 45 + FSTRaK/Models/Entity/Flight.cs | 110 +- .../Entity/FlightEvent/BaseFlightEvent.cs | 19 + .../Models/Entity/FlightEvent/CrashEvent.cs | 10 +- .../FlightEvent/FlapsSpeedExceededEvent.cs | 8 +- .../Entity/FlightEvent/FlightEndedEvent.cs | 2 + .../Entity/FlightEvent/FlightStartedEvent.cs | 2 + .../FlightEvent/GearspeedExceededEvent.cs | 8 +- .../Models/Entity/FlightEvent/LandingEvent.cs | 19 +- .../Entity/FlightEvent/OverspeedEvent.cs | 9 +- .../Models/Entity/FlightEvent/ParkingEvent.cs | 4 + .../Models/Entity/FlightEvent/ScoringEvent.cs | 3 +- .../Entity/FlightEvent/StallWarningEvent.cs | 9 +- .../Models/Entity/FlightEvent/TakeoffEvent.cs | 5 + .../Models/Entity/FlightEvent/TaxiInEvent.cs | 2 + .../Models/Entity/FlightEvent/TaxiOutEvent.cs | 2 + FSTRaK/Models/Entity/LogbookContext.cs | 2 + FSTRaK/Models/FlightManager/FlightManager.cs | 10 +- .../FlightManager/State/AbstractState.cs | 5 +- .../FlightManager/State/CrashedState.cs | 2 +- .../FlightManager/State/FlightEndedState.cs | 34 +- .../FlightManager/State/FlightStartedState.cs | 106 +- .../Models/FlightManager/State/FlightState.cs | 12 +- .../Models/FlightManager/State/LandedState.cs | 14 +- .../FlightManager/State/TakeoffRollState.cs | 4 +- .../Models/FlightManager/State/TaxiInState.cs | 4 +- .../FlightManager/State/TaxiOutState.cs | 4 +- .../SimConnectService/SimConnectService.cs | 82 +- .../FSTRaK.Models.FlightEvent.datasource | 10 - FSTRaK/Resources/ButtonsTheme.xaml | 58 +- FSTRaK/Resources/Data/airports.json | 346706 +++++++++++++++ FSTRaK/Resources/Images.xaml | 50 + FSTRaK/Resources/Theme.xaml | 103 +- FSTRaK/Utils/FlightExtentionMethods.cs | 36 + FSTRaK/Utils/ResourceUtils.cs | 19 + FSTRaK/ViewModels/BaseViewModel.cs | 3 +- FSTRaK/ViewModels/FlightDetailsViewModel.cs | 240 +- FSTRaK/ViewModels/LiveViewViewModel.cs | 56 +- FSTRaK/ViewModels/LogbookViewModel.cs | 96 +- FSTRaK/ViewModels/SettingsViewModel.cs | 10 +- FSTRaK/Views/FlightDetailsView.xaml | 88 +- FSTRaK/Views/FlightDetailsView.xaml.cs | 92 +- FSTRaK/Views/LiveView.xaml | 2 +- FSTRaK/Views/LogbookView.xaml | 144 +- FSTRaK/Views/LogbookView.xaml.cs | 2 +- FSTRaK/Views/MainWindow.xaml | 4 +- FSTRaK/Views/MainWindow.xaml.cs | 32 +- FSTRaK/Views/OverlayTextCardControl.xaml | 2 +- FSTRaK/Views/OverlayTextCardControl.xaml.cs | 2 +- FSTrAkSetup/FSTrAkSetup.vdproj | 116 +- 58 files changed, 348195 insertions(+), 507 deletions(-) create mode 100644 FSTRaK/Models/AirportResolver.cs create mode 100644 FSTRaK/Models/Entity/Airport.cs delete mode 100644 FSTRaK/Properties/DataSources/FSTRaK.Models.FlightEvent.datasource create mode 100644 FSTRaK/Resources/Data/airports.json create mode 100644 FSTRaK/Utils/FlightExtentionMethods.cs create mode 100644 FSTRaK/Utils/ResourceUtils.cs diff --git a/FSTRaK.sln b/FSTRaK.sln index 26c8e1c..bbb379e 100644 --- a/FSTRaK.sln +++ b/FSTRaK.sln @@ -15,38 +15,23 @@ Global Release|Any CPU = Release|Any CPU Release|x64 = Release|x64 Release|x86 = Release|x86 - x64|Any CPU = x64|Any CPU - x64|x64 = x64|x64 - x64|x86 = x64|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {B23FF8BA-B0CF-4388-9B13-5AB81059D6AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B23FF8BA-B0CF-4388-9B13-5AB81059D6AB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B23FF8BA-B0CF-4388-9B13-5AB81059D6AB}.Debug|Any CPU.ActiveCfg = Debug|x64 {B23FF8BA-B0CF-4388-9B13-5AB81059D6AB}.Debug|x64.ActiveCfg = Debug|x64 {B23FF8BA-B0CF-4388-9B13-5AB81059D6AB}.Debug|x64.Build.0 = Debug|x64 - {B23FF8BA-B0CF-4388-9B13-5AB81059D6AB}.Debug|x86.ActiveCfg = Debug|x86 - {B23FF8BA-B0CF-4388-9B13-5AB81059D6AB}.Debug|x86.Build.0 = Debug|x86 - {B23FF8BA-B0CF-4388-9B13-5AB81059D6AB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B23FF8BA-B0CF-4388-9B13-5AB81059D6AB}.Release|Any CPU.Build.0 = Release|Any CPU + {B23FF8BA-B0CF-4388-9B13-5AB81059D6AB}.Debug|x86.ActiveCfg = Debug|x64 + {B23FF8BA-B0CF-4388-9B13-5AB81059D6AB}.Release|Any CPU.ActiveCfg = Release|x64 {B23FF8BA-B0CF-4388-9B13-5AB81059D6AB}.Release|x64.ActiveCfg = Release|x64 {B23FF8BA-B0CF-4388-9B13-5AB81059D6AB}.Release|x64.Build.0 = Release|x64 - {B23FF8BA-B0CF-4388-9B13-5AB81059D6AB}.Release|x86.ActiveCfg = Release|x86 - {B23FF8BA-B0CF-4388-9B13-5AB81059D6AB}.Release|x86.Build.0 = Release|x86 - {B23FF8BA-B0CF-4388-9B13-5AB81059D6AB}.x64|Any CPU.ActiveCfg = Release|Any CPU - {B23FF8BA-B0CF-4388-9B13-5AB81059D6AB}.x64|Any CPU.Build.0 = Release|Any CPU - {B23FF8BA-B0CF-4388-9B13-5AB81059D6AB}.x64|x64.ActiveCfg = Release|x64 - {B23FF8BA-B0CF-4388-9B13-5AB81059D6AB}.x64|x64.Build.0 = Release|x64 - {B23FF8BA-B0CF-4388-9B13-5AB81059D6AB}.x64|x86.ActiveCfg = Release|x86 - {B23FF8BA-B0CF-4388-9B13-5AB81059D6AB}.x64|x86.Build.0 = Release|x86 + {B23FF8BA-B0CF-4388-9B13-5AB81059D6AB}.Release|x86.ActiveCfg = Release|x64 {6040B889-5FA2-4BDC-B58F-8E84301C6D1B}.Debug|Any CPU.ActiveCfg = Debug - {6040B889-5FA2-4BDC-B58F-8E84301C6D1B}.Debug|x64.ActiveCfg = Debug + {6040B889-5FA2-4BDC-B58F-8E84301C6D1B}.Debug|x64.ActiveCfg = Release + {6040B889-5FA2-4BDC-B58F-8E84301C6D1B}.Debug|x64.Build.0 = Release {6040B889-5FA2-4BDC-B58F-8E84301C6D1B}.Debug|x86.ActiveCfg = Debug {6040B889-5FA2-4BDC-B58F-8E84301C6D1B}.Release|Any CPU.ActiveCfg = Release {6040B889-5FA2-4BDC-B58F-8E84301C6D1B}.Release|x64.ActiveCfg = Release {6040B889-5FA2-4BDC-B58F-8E84301C6D1B}.Release|x86.ActiveCfg = Release - {6040B889-5FA2-4BDC-B58F-8E84301C6D1B}.x64|Any CPU.ActiveCfg = Debug - {6040B889-5FA2-4BDC-B58F-8E84301C6D1B}.x64|x64.ActiveCfg = Debug - {6040B889-5FA2-4BDC-B58F-8E84301C6D1B}.x64|x86.ActiveCfg = Debug EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/FSTRaK/App.xaml b/FSTRaK/App.xaml index 7437841..58256ff 100644 --- a/FSTRaK/App.xaml +++ b/FSTRaK/App.xaml @@ -4,6 +4,7 @@ xmlns:local="clr-namespace:FSTRaK" Startup="OnApplicationStart" Exit="OnApplicationExit" + DispatcherUnhandledException="App_DispatcherUnhandledException" StartupUri="Views/MainWindow.xaml"> diff --git a/FSTRaK/App.xaml.cs b/FSTRaK/App.xaml.cs index 0add91d..2d9bfb6 100644 --- a/FSTRaK/App.xaml.cs +++ b/FSTRaK/App.xaml.cs @@ -1,7 +1,12 @@ -using FSTRaK.Models.Entity; +using FSTRaK.Models; +using FSTRaK.Models.Entity; using Serilog; +using Serilog.Exceptions; +using System; +using System.Threading; using System.Threading.Tasks; using System.Windows; +using System.Windows.Threading; namespace FSTRaK { @@ -10,9 +15,30 @@ namespace FSTRaK /// public partial class App : Application { - void OnApplicationStart(object sender, StartupEventArgs args) + private static Mutex _mutex = null; + + + + const string appName = "FSTrAk"; + + protected override void OnStartup(StartupEventArgs e) + { + _mutex = new Mutex(true, appName, out var createdNew); + + if (!createdNew) + { + MessageBox.Show("An instance of FSTrAk is already running...", "FSTrAk"); + Application.Current.Shutdown(); + } + + base.OnStartup(e); + } + + + void OnApplicationStart(object sender, StartupEventArgs args) { Log.Logger = new LoggerConfiguration() + .Enrich.WithExceptionDetails() .MinimumLevel.Information() #if DEBUG .MinimumLevel.Debug() @@ -25,9 +51,18 @@ void OnApplicationStart(object sender, StartupEventArgs args) { using (var logbookContext = new LogbookContext()) { - logbookContext.Aircraft.Find(1); + try + { + logbookContext.Aircraft.Find(1); + } + catch (Exception ex) + { + Log.Error(ex, ex.Message); + } } }); + + var airportResolder = AirportResolver.Instance; } @@ -40,5 +75,12 @@ void OnApplicationExit(object sender, ExitEventArgs e) } FSTRaK.Properties.Settings.Default.Save(); } + + void App_DispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e) + { + Log.Error(e.Exception, "Unhandled error occured!"); + // Prevent default unhandled exception processing + e.Handled = true; + } } } diff --git a/FSTRaK/DataTypes/SimConnectDataTypes.cs b/FSTRaK/DataTypes/SimConnectDataTypes.cs index 666435e..e48ddc6 100644 --- a/FSTRaK/DataTypes/SimConnectDataTypes.cs +++ b/FSTRaK/DataTypes/SimConnectDataTypes.cs @@ -1,4 +1,5 @@  +using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; @@ -71,9 +72,10 @@ public enum DataDefinitions public enum EVENTS { - FLIGHT_LOADED, - PAUSE, - CRASHED + FlightLoaded, + AircraftLoaded, + Pause, + Crashed } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)] @@ -124,6 +126,10 @@ public struct AircraftFlightData public double Engine3MaxRpmPct; public double Engine4MaxRpmPct; + public double Throttle1Position; + public double Throttle2Position; + public double Throttle3Position; + public double Throttle4Position; /// /// Used to determine negine start up @@ -134,6 +140,19 @@ public double MaxEngineRpmPct() return new double[] { Engine1MaxRpmPct, Engine2MaxRpmPct, Engine3MaxRpmPct, Engine4MaxRpmPct}.Max(); } + public double MaxThorttlePosition() + { + return new double[] { Throttle1Position, Throttle2Position, Throttle3Position, Throttle3Position }.Max(); + } + + public double MinThrottlePosition() + { + var thorttlePositionArray = new List( new double[] { Throttle1Position, Throttle1Position, Throttle2Position, Throttle3Position }); + if (NumberOfEngines == 0) + return 0; + return thorttlePositionArray.GetRange(0,NumberOfEngines).Min(); + } + } internal class SimConnectDataTypes { diff --git a/FSTRaK/FSTRaK.csproj b/FSTRaK/FSTRaK.csproj index 7e2c697..75b9373 100644 --- a/FSTRaK/FSTRaK.csproj +++ b/FSTRaK/FSTRaK.csproj @@ -28,31 +28,12 @@ false false true - 3 + 4 1.0.0.%2a false true true - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - true bin\x64\Debug\ @@ -62,6 +43,9 @@ 7.3 prompt true + false + + bin\x64\Release\ @@ -77,7 +61,8 @@ 53F640C03978D30A7C6C1530038EC1B18B8D605E - FSTrAk_TemporaryKey.pfx + + true @@ -85,29 +70,12 @@ true - - true - bin\x86\Debug\ - DEBUG;TRACE - full - x86 - 7.3 - prompt - true - - - bin\x86\Release\ - TRACE - true - pdbonly - x86 - 7.3 - prompt - true - FSTrAk.ico + + FSTRaK.App + False @@ -151,6 +119,8 @@ + + @@ -183,7 +153,9 @@ + + @@ -282,7 +254,6 @@ - @@ -320,6 +291,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest @@ -347,15 +321,24 @@ 1.1.39 + + 4.1.64 + 2.12.0 + + 8.4.0 + 5.0.0 3.0.0 + + 7.0.2 + 8.1.0 diff --git a/FSTRaK/FSTrAk.csproj b/FSTRaK/FSTrAk.csproj index 703e460..75b9373 100644 --- a/FSTRaK/FSTrAk.csproj +++ b/FSTRaK/FSTrAk.csproj @@ -28,31 +28,12 @@ false false true - 3 + 4 1.0.0.%2a false true true - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - true bin\x64\Debug\ @@ -89,26 +70,6 @@ true - - true - bin\x86\Debug\ - DEBUG;TRACE - full - x86 - 7.3 - prompt - true - - - bin\x86\Release\ - TRACE - true - pdbonly - x86 - 7.3 - prompt - true - FSTrAk.ico @@ -158,6 +119,8 @@ + + @@ -190,7 +153,9 @@ + + @@ -289,7 +254,6 @@ - @@ -327,6 +291,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest @@ -354,15 +321,24 @@ 1.1.39 + + 4.1.64 + 2.12.0 + + 8.4.0 + 5.0.0 3.0.0 + + 7.0.2 + 8.1.0 diff --git a/FSTRaK/Models/AirportResolver.cs b/FSTRaK/Models/AirportResolver.cs new file mode 100644 index 0000000..9331dcf --- /dev/null +++ b/FSTRaK/Models/AirportResolver.cs @@ -0,0 +1,49 @@ +using FSTRaK.Models.Entity; +using Serilog; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; + +namespace FSTRaK.Models +{ + internal class AirportResolver + { + private static readonly object _lock = new object(); + private static AirportResolver instance = null; + + public Dictionary AirportsDictionary; + + private AirportResolver() + { + LoadAirportsJson(); + } + + private async void LoadAirportsJson() + { + var openStream = File.OpenRead(@".\Resources\\Data\\airports.json"); + AirportsDictionary = + await JsonSerializer.DeserializeAsync>(openStream); + openStream.Close(); + Log.Information($"{AirportsDictionary.Count} airports loaded."); + } + + public static AirportResolver Instance + { + get + { + lock (_lock) + { + if (instance == null) + { + instance = new AirportResolver(); + } + return instance; + } + } + } + } +} diff --git a/FSTRaK/Models/Entity/Aircraft.cs b/FSTRaK/Models/Entity/Aircraft.cs index d69e0ea..81c0ef7 100644 --- a/FSTRaK/Models/Entity/Aircraft.cs +++ b/FSTRaK/Models/Entity/Aircraft.cs @@ -1,8 +1,8 @@ using FSTRaK.DataTypes; using System; using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using System.Text; namespace FSTRaK.Models { @@ -14,7 +14,10 @@ internal class Aircraft : BaseModel public string Title { get; set; } public String AircraftType { get; set; } - + + public String Manufacturer { get; set; } + + public String Airline { get; set; } public String Model { get; set; } public String TailNumber { get; set; } @@ -35,9 +38,20 @@ public override bool Equals(Object obj) } } + public override string ToString() + { + var builder = new StringBuilder(); + builder.AppendLine(this.Title) + .AppendLine($"{this.Manufacturer} {this.AircraftType}") + .AppendLine(this.TailNumber); + if (this.Airline != string.Empty) + builder.AppendLine(this.Airline); + return builder.ToString(); + } + public override int GetHashCode() { - int hashCode = -702049157; + var hashCode = -702049157; hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Title); hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Model); return hashCode; diff --git a/FSTRaK/Models/Entity/Airport.cs b/FSTRaK/Models/Entity/Airport.cs new file mode 100644 index 0000000..f857a6d --- /dev/null +++ b/FSTRaK/Models/Entity/Airport.cs @@ -0,0 +1,45 @@ + +using System.Globalization; +using System.Linq; +using System.Text; + +namespace FSTRaK.Models.Entity +{ + internal class Airport + { + public string icao { set; get; } + public string iata { set; get; } + public string name { set; get; } + public string city { set; get; } + public string state { set; get; } + public string country { set; get; } + + public int elevation { set; get; } + public double lat { set; get; } + public double lon { set; get; } + public string tz { set; get; } + + public string CountryName { + get + { + var cultureInfo = new CultureInfo(country.ToLower()); + var ri = new RegionInfo(cultureInfo.Name); + return ri.EnglishName; + } + } + + public override string ToString() + { + if(name == string.Empty || name == null) { + return icao; + } + StringBuilder sb = new StringBuilder(); + sb.Append(city); + if (country == "US") + sb.Append(", ").Append($"{state}, USA"); + else sb.Append(", ").Append(CountryName); + sb.Append($" ({icao})"); + return sb.ToString(); + } + } +} diff --git a/FSTRaK/Models/Entity/Flight.cs b/FSTRaK/Models/Entity/Flight.cs index 85a11fc..91019ba 100644 --- a/FSTRaK/Models/Entity/Flight.cs +++ b/FSTRaK/Models/Entity/Flight.cs @@ -1,7 +1,12 @@ using FSTRaK.DataTypes; +using FSTRaK.Models.Entity; +using FSTRaK.Utils; using System; +using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Text; namespace FSTRaK.Models { @@ -29,7 +34,7 @@ public TimeSpan FlightTime set { FlightTimeMilis = value.Ticks; } } - public double FlightDistanceInMeters { get; set; } + public double FlightDistanceNM { get; set; } public double TotalFuelUsed { get; set; } @@ -40,6 +45,41 @@ public TimeSpan FlightTime public ObservableCollection FlightEvents { get; private set; } + [NotMapped] public Airport DepartureAirportDetails + { + get + { + try + { + var airport = AirportResolver.Instance.AirportsDictionary[DepartureAirport]; + return airport; + } + catch (Exception) + { + return new Airport + { + icao = DepartureAirport + }; + } + + } + } + + [NotMapped] + public Airport ArrivalAirportDetails + { + get + { + var airport = AirportResolver.Instance.AirportsDictionary[ArrivalAirport]; + if (airport == null) + airport = new Airport + { + icao = ArrivalAirport + }; + return airport; + } + } + public Flight() { this.FlightEvents = new ObservableCollection(); @@ -47,8 +87,74 @@ public Flight() public override string ToString() { - return ID.ToString(); + var sb = new StringBuilder(); + sb.AppendLine($"Departed From: {this.DepartureAirportDetails}"); + + if (this.FlightOutcome == FlightOutcome.Crashed) + { + sb.AppendLine(($"Crashed Near {this.ArrivalAirportDetails}")); + } + else + { + sb.AppendLine(($"Arrived At: {this.ArrivalAirportDetails}")); + } + + sb.AppendLine($"Start Time: {this.StartTime}") + .AppendLine($"End Time: {this.EndTime}") + .AppendLine($"Block Time: {this.FlightTime}") + .AppendLine($"Fuel Used: {TotalFuelUsed:F1}") + .AppendLine($"Flown Distance: {FlightDistanceNM:F0} NM"); + + var landingEvent = (LandingEvent)this.FlightEvents.FirstOrDefault(e => e is LandingEvent); + if (landingEvent != null) + { + sb.AppendLine($"Lnading VS: {landingEvent.VerticalSpeed:F0} ft/m"); + } + + + sb.AppendLine($"Score: {this.Score}") + .ToString(); + + return sb.ToString(); + } + + /// + /// To be called after the flight is concluded. This method calculates the total score of the flight so it can be persisted. + /// + public void UpdateScore() + { + var scoringEvents = GetScoringEvents(); + Score = 100 - scoringEvents.Sum(e => e.ScoreDelta); } + private List GetScoringEvents() + { + return this.FlightEvents + .OfType() + .GroupBy(e => e.GetType()) + .Select(e => (ScoringEvent)e.First()) + .ToList(); + } + + public string GetScoreDetails() + { + var scoringEvents = GetScoringEvents(); + var builder = new StringBuilder(); + foreach(var se in scoringEvents) + { + if(se.ScoreDelta != 0) + { + if(se is LandingEvent) + { + builder.AppendLine($"{((LandingEvent)se).LandingRate} {se.EventName} {se.ScoreDelta} Points"); + } + else + { + builder.AppendLine($"{se.EventName} {se.ScoreDelta} Points"); + } + } + } + return builder.ToString(); + } } } diff --git a/FSTRaK/Models/Entity/FlightEvent/BaseFlightEvent.cs b/FSTRaK/Models/Entity/FlightEvent/BaseFlightEvent.cs index e95a14b..c27e4a2 100644 --- a/FSTRaK/Models/Entity/FlightEvent/BaseFlightEvent.cs +++ b/FSTRaK/Models/Entity/FlightEvent/BaseFlightEvent.cs @@ -3,6 +3,7 @@ namespace FSTRaK.Models { + [Table("FlightEvent")] internal class BaseFlightEvent : BaseModel { @@ -20,5 +21,23 @@ internal class BaseFlightEvent : BaseModel public int FlightID { get; set; } public virtual Flight Flight { get; set; } + + [NotMapped] public virtual string EventName { get; set; } = "Flight event"; + + + [NotMapped] + public string Location + { + get + { + return $"{Latitude},{Longitude}"; + } + private set { } + } + + public override string ToString() + { + return $"{EventName}\n{Time.ToShortTimeString()}"; + } } } diff --git a/FSTRaK/Models/Entity/FlightEvent/CrashEvent.cs b/FSTRaK/Models/Entity/FlightEvent/CrashEvent.cs index a915824..7cdb1fd 100644 --- a/FSTRaK/Models/Entity/FlightEvent/CrashEvent.cs +++ b/FSTRaK/Models/Entity/FlightEvent/CrashEvent.cs @@ -1,8 +1,16 @@  +using System.ComponentModel.DataAnnotations.Schema; + namespace FSTRaK.Models { internal class CrashEvent : ScoringEvent { - public override int ScoreDelta { get; set; } = -100; + + [NotMapped] public override string EventName { get; set; } = "Crashed"; + + public CrashEvent() + { + ScoreDelta = -100; + } } } diff --git a/FSTRaK/Models/Entity/FlightEvent/FlapsSpeedExceededEvent.cs b/FSTRaK/Models/Entity/FlightEvent/FlapsSpeedExceededEvent.cs index e8b26ec..9c2c6e7 100644 --- a/FSTRaK/Models/Entity/FlightEvent/FlapsSpeedExceededEvent.cs +++ b/FSTRaK/Models/Entity/FlightEvent/FlapsSpeedExceededEvent.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -8,6 +9,11 @@ namespace FSTRaK.Models.Entity.FlightEvent { internal class FlapsSpeedExceededEvent : ScoringEvent { - public override int ScoreDelta { get; set; } = -5; + [NotMapped] public override string EventName { get; set; } = "Flaps speed exceeded"; + + public FlapsSpeedExceededEvent() + { + ScoreDelta = -5; + } } } diff --git a/FSTRaK/Models/Entity/FlightEvent/FlightEndedEvent.cs b/FSTRaK/Models/Entity/FlightEvent/FlightEndedEvent.cs index 24e855b..a2f847d 100644 --- a/FSTRaK/Models/Entity/FlightEvent/FlightEndedEvent.cs +++ b/FSTRaK/Models/Entity/FlightEvent/FlightEndedEvent.cs @@ -1,9 +1,11 @@ using System; +using System.ComponentModel.DataAnnotations.Schema; namespace FSTRaK.Models { internal class FlightEndedEvent : BaseFlightEvent { + [Column("FuelWeightLbs")] public double FuelWeightLbs { get; set; } } diff --git a/FSTRaK/Models/Entity/FlightEvent/FlightStartedEvent.cs b/FSTRaK/Models/Entity/FlightEvent/FlightStartedEvent.cs index 82c5052..d7df029 100644 --- a/FSTRaK/Models/Entity/FlightEvent/FlightStartedEvent.cs +++ b/FSTRaK/Models/Entity/FlightEvent/FlightStartedEvent.cs @@ -1,9 +1,11 @@ using System; +using System.ComponentModel.DataAnnotations.Schema; namespace FSTRaK.Models { internal class FlightStartedEvent : BaseFlightEvent { + [Column("FuelWeightLbs")] public double FuelWeightLbs { get; set; } } diff --git a/FSTRaK/Models/Entity/FlightEvent/GearspeedExceededEvent.cs b/FSTRaK/Models/Entity/FlightEvent/GearspeedExceededEvent.cs index f0fc37f..a1f8d89 100644 --- a/FSTRaK/Models/Entity/FlightEvent/GearspeedExceededEvent.cs +++ b/FSTRaK/Models/Entity/FlightEvent/GearspeedExceededEvent.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -8,6 +9,11 @@ namespace FSTRaK.Models.Entity.FlightEvent { internal class GearsSpeedExceededEvent : ScoringEvent { - public override int ScoreDelta { get; set; } = -10; + [NotMapped] public override string EventName { get; set; } = "Gear speed exceeded"; + + public GearsSpeedExceededEvent() { + ScoreDelta = -10; + } + } } diff --git a/FSTRaK/Models/Entity/FlightEvent/LandingEvent.cs b/FSTRaK/Models/Entity/FlightEvent/LandingEvent.cs index c981c52..56ac584 100644 --- a/FSTRaK/Models/Entity/FlightEvent/LandingEvent.cs +++ b/FSTRaK/Models/Entity/FlightEvent/LandingEvent.cs @@ -1,22 +1,29 @@ using FSTRaK.DataTypes; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.ComponentModel.DataAnnotations.Schema; + namespace FSTRaK.Models { internal class LandingEvent : ScoringEvent { + [Column("FlapsPosition")] public double FlapsPosition { get; set; } public double VerticalSpeed { get; set; } + + [Column("FuelWeightLbs")] public double FuelWeightLbs { get; set; } public LandingRate LandingRate { get; set; } public double TouchDownPitchDegrees { get; set; } public double TouchDownBankDegress { get; set; } - public override int ScoreDelta { get; set; } + + [NotMapped] public override string EventName { get; set; } = "Landing"; + + public override string ToString() + { + return $"{LandingRate}\n" + base.ToString() + $"\n{VerticalSpeed:F0} fpm"; + } + } } diff --git a/FSTRaK/Models/Entity/FlightEvent/OverspeedEvent.cs b/FSTRaK/Models/Entity/FlightEvent/OverspeedEvent.cs index 7eb751c..1bc40ef 100644 --- a/FSTRaK/Models/Entity/FlightEvent/OverspeedEvent.cs +++ b/FSTRaK/Models/Entity/FlightEvent/OverspeedEvent.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -8,6 +9,12 @@ namespace FSTRaK.Models.Entity.FlightEvent { internal class OverspeedEvent : ScoringEvent { - public override int ScoreDelta { get; set; } = -15; + + [NotMapped] public override string EventName { get; set; } = "Overspeed"; + + public OverspeedEvent() { + ScoreDelta = -15; + } + } } diff --git a/FSTRaK/Models/Entity/FlightEvent/ParkingEvent.cs b/FSTRaK/Models/Entity/FlightEvent/ParkingEvent.cs index 98e3ef8..3661938 100644 --- a/FSTRaK/Models/Entity/FlightEvent/ParkingEvent.cs +++ b/FSTRaK/Models/Entity/FlightEvent/ParkingEvent.cs @@ -1,9 +1,13 @@  +using System.ComponentModel.DataAnnotations.Schema; + namespace FSTRaK.Models { internal class ParkingEvent : BaseFlightEvent { + [Column("FlapsPosition")] public double FlapsPosition { get; set; } + [Column("FuelWeightLbs")] public double FuelWeightLbs { get; set; } } } diff --git a/FSTRaK/Models/Entity/FlightEvent/ScoringEvent.cs b/FSTRaK/Models/Entity/FlightEvent/ScoringEvent.cs index 4ed19a0..e285366 100644 --- a/FSTRaK/Models/Entity/FlightEvent/ScoringEvent.cs +++ b/FSTRaK/Models/Entity/FlightEvent/ScoringEvent.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel.DataAnnotations.Schema; namespace FSTRaK.Models { @@ -7,6 +8,6 @@ namespace FSTRaK.Models /// internal abstract class ScoringEvent : BaseFlightEvent { - public abstract int ScoreDelta { get; set; } + public int ScoreDelta { get; set; } = 0; } } diff --git a/FSTRaK/Models/Entity/FlightEvent/StallWarningEvent.cs b/FSTRaK/Models/Entity/FlightEvent/StallWarningEvent.cs index c6e1298..2cfecf0 100644 --- a/FSTRaK/Models/Entity/FlightEvent/StallWarningEvent.cs +++ b/FSTRaK/Models/Entity/FlightEvent/StallWarningEvent.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -8,6 +9,12 @@ namespace FSTRaK.Models.Entity.FlightEvent { internal class StallWarningEvent : ScoringEvent { - public override int ScoreDelta { get; set; } = -20; + + [NotMapped] public override string EventName { get; set; } = "Stalled"; + public StallWarningEvent() + { + ScoreDelta = -20; + } + } } diff --git a/FSTRaK/Models/Entity/FlightEvent/TakeoffEvent.cs b/FSTRaK/Models/Entity/FlightEvent/TakeoffEvent.cs index 0b63f94..9555931 100644 --- a/FSTRaK/Models/Entity/FlightEvent/TakeoffEvent.cs +++ b/FSTRaK/Models/Entity/FlightEvent/TakeoffEvent.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -8,8 +9,12 @@ namespace FSTRaK.Models { internal class TakeoffEvent : BaseFlightEvent { + [Column("FlapsPosition")] public double FlapsPosition { get; set; } + [Column("FuelWeightLbs")] public double FuelWeightLbs { get; set; } + [NotMapped] public override string EventName { get; set; } = "Takeoff"; + } } diff --git a/FSTRaK/Models/Entity/FlightEvent/TaxiInEvent.cs b/FSTRaK/Models/Entity/FlightEvent/TaxiInEvent.cs index 56196e5..148b10f 100644 --- a/FSTRaK/Models/Entity/FlightEvent/TaxiInEvent.cs +++ b/FSTRaK/Models/Entity/FlightEvent/TaxiInEvent.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -8,6 +9,7 @@ namespace FSTRaK.Models { internal class TaxiInEvent : BaseFlightEvent { + [Column("FuelWeightLbs")] public double FuelWeightLbs { get; set; } } } diff --git a/FSTRaK/Models/Entity/FlightEvent/TaxiOutEvent.cs b/FSTRaK/Models/Entity/FlightEvent/TaxiOutEvent.cs index e272dc2..7c41a2a 100644 --- a/FSTRaK/Models/Entity/FlightEvent/TaxiOutEvent.cs +++ b/FSTRaK/Models/Entity/FlightEvent/TaxiOutEvent.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -8,6 +9,7 @@ namespace FSTRaK.Models { internal class TaxiOutEvent : BaseFlightEvent { + [Column("FuelWeightLbs")] public double FuelWeightLbs { get; set; } } } diff --git a/FSTRaK/Models/Entity/LogbookContext.cs b/FSTRaK/Models/Entity/LogbookContext.cs index 34c2bf6..a8375d3 100644 --- a/FSTRaK/Models/Entity/LogbookContext.cs +++ b/FSTRaK/Models/Entity/LogbookContext.cs @@ -1,6 +1,7 @@  +using Serilog; using System.Data.Entity; using System.Data.Entity.ModelConfiguration.Conventions; @@ -13,6 +14,7 @@ internal class LogbookContext : DbContext public LogbookContext() : base("FSTrAkCompactDatabase") { Database.SetInitializer(new MigrateDatabaseToLatestVersion()); + this.Database.Log = s => Log.Debug(s); } public DbSet Flights { get; set; } diff --git a/FSTRaK/Models/FlightManager/FlightManager.cs b/FSTRaK/Models/FlightManager/FlightManager.cs index 8a3006d..ac59a39 100644 --- a/FSTRaK/Models/FlightManager/FlightManager.cs +++ b/FSTRaK/Models/FlightManager/FlightManager.cs @@ -104,7 +104,7 @@ private void SimconnectService_OnPropertyChange(object sender, PropertyChangedEv // Updating the map in realtime if not in non-flight states if (!(State is SimNotInFlightState)) { - FlightParams fp = new FlightParams(); + var fp = new FlightParams(); fp.IndicatedAirspeed = data.IndicatedAirpeed; fp.GroundSpeed = data.GroundVelocity; fp.VeticalSpeed = data.VerticalSpeed; @@ -123,7 +123,6 @@ private void SimconnectService_OnPropertyChange(object sender, PropertyChangedEv var airport = _simConnectService.NearestAirport; if(ActiveFlight != null && CurrentFlightParams.IsOnGround) { - Log.Debug($"xxx {airport} requested for {NearestAirportRequestType.Departure}"); if(_nearestAirportRequestType == NearestAirportRequestType.Departure) { ActiveFlight.DepartureAirport = airport; @@ -132,6 +131,8 @@ private void SimconnectService_OnPropertyChange(object sender, PropertyChangedEv { ActiveFlight.ArrivalAirport = airport; } + var prefix = (_nearestAirportRequestType == NearestAirportRequestType.Departure) ? "Departing" : "Landed"; + Log.Information($"{prefix} - found {airport} at {_simConnectService.NearestAirportDistance * Consts.MetersToNauticalMiles} NM"); } break; @@ -141,6 +142,11 @@ private void SimconnectService_OnPropertyChange(object sender, PropertyChangedEv } } + public string GetLoadedAircraftFileName() + { + return _simConnectService.LoadedAircraft; + } + internal void RequestNearestAirports(NearestAirportRequestType nearestAirportRequestType) { _nearestAirportRequestType = nearestAirportRequestType; diff --git a/FSTRaK/Models/FlightManager/State/AbstractState.cs b/FSTRaK/Models/FlightManager/State/AbstractState.cs index 9416787..50d483e 100644 --- a/FSTRaK/Models/FlightManager/State/AbstractState.cs +++ b/FSTRaK/Models/FlightManager/State/AbstractState.cs @@ -54,7 +54,7 @@ public virtual void HandleFlightExitEvent() /// protected void AddFlightEvent(AircraftFlightData data, BaseFlightEvent fe) { - DateTime time = CalculateSimTime(data); + var time = CalculateSimTime(data); fe.Altitude = data.Altitude; fe.GroundAltitude = data.GroundAltitude; fe.Latitude = data.Latitude; @@ -64,12 +64,11 @@ protected void AddFlightEvent(AircraftFlightData data, BaseFlightEvent fe) fe.GroundSpeed = data.GroundVelocity; fe.Time = time; Context.ActiveFlight.FlightEvents.Add(fe); - Log.Debug(Context.ActiveFlight.FlightEvents.Last().GetType().ToString()); } protected void AddFlightEvent(AircraftFlightData data) { - BaseFlightEvent fe = new BaseFlightEvent(); + var fe = new BaseFlightEvent(); AddFlightEvent(data, fe); } diff --git a/FSTRaK/Models/FlightManager/State/CrashedState.cs b/FSTRaK/Models/FlightManager/State/CrashedState.cs index 34dffcd..5e799e9 100644 --- a/FSTRaK/Models/FlightManager/State/CrashedState.cs +++ b/FSTRaK/Models/FlightManager/State/CrashedState.cs @@ -17,7 +17,7 @@ public CrashedState(FlightManager Context) : base(Context) } public override void ProcessFlightData(AircraftFlightData Data) { - CrashEvent ce = new CrashEvent(); + var ce = new CrashEvent(); AddFlightEvent(Data, ce); Context.State = new FlightEndedState(Context); diff --git a/FSTRaK/Models/FlightManager/State/FlightEndedState.cs b/FSTRaK/Models/FlightManager/State/FlightEndedState.cs index 0f5b156..f4ade92 100644 --- a/FSTRaK/Models/FlightManager/State/FlightEndedState.cs +++ b/FSTRaK/Models/FlightManager/State/FlightEndedState.cs @@ -29,7 +29,7 @@ public override void ProcessFlightData(AircraftFlightData Data) { if(!_isEnded) { - FlightEndedEvent fe = new FlightEndedEvent + var fe = new FlightEndedEvent { FuelWeightLbs = Data.FuelWeightLbs }; @@ -55,9 +55,9 @@ public override void ProcessFlightData(AircraftFlightData Data) } Context.ActiveFlight.FlightTime = flightTime; - Context.ActiveFlight.FlightDistanceInMeters = FlightPathLength(Context.ActiveFlight.FlightEvents); + Context.ActiveFlight.FlightDistanceNM = FlightPathLength(Context.ActiveFlight.FlightEvents) * Consts.MetersToNauticalMiles; ; - CalculateScore(); + Context.ActiveFlight.UpdateScore(); if (Context.ActiveFlight.FlightOutcome == FlightOutcome.Completed || !Properties.Settings.Default.IsSaveOnlyCompleteFlights) { @@ -72,25 +72,6 @@ public override void ProcessFlightData(AircraftFlightData Data) } } - private void CalculateScore() - { - var uniqueScoringEvents = Context.ActiveFlight.FlightEvents - .OfType() - .GroupBy(e => e.GetType()) - .Select(e => (ScoringEvent)e.First()) - .ToList(); - - var scoringDelta = uniqueScoringEvents.Sum(e => e.ScoreDelta); - - Context.ActiveFlight.Score = MathUtils.Clamp(100 + scoringDelta, 0, 100); - - StringBuilder sb = new StringBuilder(); - foreach (var item in uniqueScoringEvents) - { - sb.AppendLine($"{item.Time} {item.GetType().ToString()} {item.ScoreDelta}"); - } - } - private void SetFlightOutcome() { if (Context.ActiveFlight.FlightEvents.Last() is ParkingEvent) @@ -110,7 +91,7 @@ private void SetFlightOutcome() private double FlightPathLength(ObservableCollection flightEvents) { double length = 0; - for ( int i = 1; i < flightEvents.Count; i++) + for ( var i = 1; i < flightEvents.Count; i++) { var start = new Location(flightEvents[i - 1].Latitude, flightEvents[i - 1].Longitude); var end = new Location(flightEvents[i].Latitude, flightEvents[i].Longitude); @@ -136,7 +117,7 @@ private Task SavetFlight() { try { - // Check if the aircraft user is already in the db + // Check if the aircraft is already in the db var aircraft = logbookContext.Aircraft.Where(a => a.Title == Context.ActiveFlight.Aircraft.Title).FirstOrDefault(); if (aircraft != null) { @@ -147,10 +128,7 @@ private Task SavetFlight() } catch (Exception ex) { - Log.Debug(ex.Message); - - Log.Debug(ex.ToString()); - Log.Debug(ex.InnerException.ToString()); + Log.Error(ex, "An error occured while trying to persist the flight!"); } } }); diff --git a/FSTRaK/Models/FlightManager/State/FlightStartedState.cs b/FSTRaK/Models/FlightManager/State/FlightStartedState.cs index 9cc7e78..ef30f9c 100644 --- a/FSTRaK/Models/FlightManager/State/FlightStartedState.cs +++ b/FSTRaK/Models/FlightManager/State/FlightStartedState.cs @@ -1,8 +1,17 @@ using FSTRaK.DataTypes; +using FSTRaK.Models.Entity; using Serilog; using System; using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Linq; using System.Runtime.Remoting.Metadata.W3cXsd2001; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Markup; +using System.Windows.Media.Media3D; namespace FSTRaK.Models.FlightManager { @@ -31,20 +40,10 @@ public override void ProcessFlightData(AircraftFlightData Data) // This should only happen once per flight if (!_isStarted) { - Flight flight = new Flight(); - Aircraft aircraft; - aircraft = new Aircraft - { - Title = Data.title, - AircraftType = Data.atcType, - Model = Data.model, - Airline = Data.airline, - TailNumber = Data.AtcId, - NumberOfEngines = Data.NumberOfEngines, - EngineType = Data.EngineType - }; - flight.Aircraft = aircraft; + var flight = new Flight(); Context.ActiveFlight = flight; + + SetAircraftAsynchronously(flight, Data); Context.RequestNearestAirports(NearestAirportRequestType.Departure); _isStarted = true; @@ -66,7 +65,7 @@ public override void ProcessFlightData(AircraftFlightData Data) { _flightStartedEvent.FuelWeightLbs = Data.FuelWeightLbs; _prevFuelQuantity = Data.FuelWeightLbs; - Log.Debug($"Fuel Quantity updated to {_flightStartedEvent.FuelWeightLbs}"); + Log.Information($"Fuel Quantity updated to {_flightStartedEvent.FuelWeightLbs}"); } } @@ -74,7 +73,7 @@ public override void ProcessFlightData(AircraftFlightData Data) if (Data.CameraState == (int)CameraState.Cockpit && (Data.Latitude != Context.CurrentFlightParams.Latitude || Data.Longitude != Context.CurrentFlightParams.Longitude) && Data.GroundVelocity > 1) { - TaxiOutEvent to = new TaxiOutEvent + var to = new TaxiOutEvent { FuelWeightLbs = Data.FuelWeightLbs }; @@ -82,6 +81,83 @@ public override void ProcessFlightData(AircraftFlightData Data) Context.State = new TaxiOutState(Context); } } + + private void SetAircraftAsynchronously(Flight flight, AircraftFlightData Data) + { + _ = Task.Run(() => + { + using (var logbookContext = new LogbookContext()) + { + Aircraft aircraft; + try + { + // If aircraft is already in the db, let's use the existing records instead. + aircraft = logbookContext.Aircraft.Where(a => a.Title == Data.title).FirstOrDefault(); + if (aircraft != null) + { + Context.ActiveFlight.Aircraft = aircraft; + } + else + { + aircraft = new Aircraft + { + Title = Data.title, + Manufacturer = Data.atcType, + Model = Data.model, + AircraftType = Data.model, + Airline = Data.airline, + TailNumber = Data.AtcId, + NumberOfEngines = Data.NumberOfEngines, + EngineType = Data.EngineType + }; + + aircraft = logbookContext.Aircraft.Add(aircraft); + flight.Aircraft = aircraft; + + // The data returned in simconnect simvars in not consistent, so we will try to fill data from the loaded aircraft file. + var filename = Context.GetLoadedAircraftFileName(); + + using (var fileStream = File.OpenRead(filename)) + using (var streamReader = new StreamReader(fileStream, Encoding.UTF8, true, 128)) + { + String line; + while ((line = streamReader.ReadLine()) != null) + { + var parts = line.Split('='); + if (parts.Length > 1) + { + if (parts[0].Trim() == "icao_type_designator") + { + aircraft.AircraftType = parts[1].Trim('"', ' ', '\t'); + } + + if (parts[0].Trim() == "icao_manufacturer") + { + aircraft.Manufacturer = parts[1].Trim('"', ' ', '\t'); + } + + if (parts[0].Trim() == "icao_model") + { + aircraft.Model = parts[1].Trim('"', ' ', '\t'); + } + } + } + // Capitalize manufacturer name correctly. + CultureInfo cultureInfo = new CultureInfo("en-US"); + TextInfo textInfo = cultureInfo.TextInfo; + aircraft.Manufacturer = textInfo.ToTitleCase(aircraft.Manufacturer.ToLower()); + + } + } + } + catch (Exception ex) + { + Log.Error(ex, "Unhandled error occured!"); + } + } + + }); + } } } diff --git a/FSTRaK/Models/FlightManager/State/FlightState.cs b/FSTRaK/Models/FlightManager/State/FlightState.cs index c90ba8a..81ef4b0 100644 --- a/FSTRaK/Models/FlightManager/State/FlightState.cs +++ b/FSTRaK/Models/FlightManager/State/FlightState.cs @@ -11,7 +11,7 @@ internal class FlightState : AbstractState public override bool IsMovementState { get; set; } public FlightState(FlightManager Context) : base(Context) { - this._eventInterval = 5000; + this._eventInterval = 10000; this.Name = "In flight"; this.IsMovementState = true; } @@ -25,15 +25,15 @@ public override void ProcessFlightData(AircraftFlightData Data) if (Data.IndicatedAirpeed < 150) { - _eventInterval = 5000; + _eventInterval = 10000; } else if (Data.IndicatedAirpeed > 150 && Data.IndicatedAirpeed < 250) { - _eventInterval = 10000; + _eventInterval = 12000; } - else if (Data.IndicatedAirpeed > 250) + else if (Data.IndicatedAirpeed > 250 || Data.Altitude > 10000) { - _eventInterval = 15000; + _eventInterval = 20000; } // TODO add code to handle specific flight events @@ -41,7 +41,7 @@ public override void ProcessFlightData(AircraftFlightData Data) // Add event if stopwatch is not started, check if interval has elapsed otherwise if (!_stopwatch.IsRunning || _stopwatch.ElapsedMilliseconds > _eventInterval) { - BaseFlightEvent fe = CheckEnvelopeExceedingEvents(Data); + var fe = CheckEnvelopeExceedingEvents(Data); AddFlightEvent(Data, fe); _stopwatch.Restart(); } diff --git a/FSTRaK/Models/FlightManager/State/LandedState.cs b/FSTRaK/Models/FlightManager/State/LandedState.cs index b54f75f..cb57c7a 100644 --- a/FSTRaK/Models/FlightManager/State/LandedState.cs +++ b/FSTRaK/Models/FlightManager/State/LandedState.cs @@ -23,7 +23,7 @@ public LandedState(FlightManager Context, AircraftFlightData LandingData) : base private void ProcessLandingData(AircraftFlightData landingData) { - LandingEvent le = new LandingEvent() + var le = new LandingEvent() { VerticalSpeed = landingData.VerticalSpeed }; @@ -31,12 +31,12 @@ private void ProcessLandingData(AircraftFlightData landingData) if (landingData.VerticalSpeed < -500) { le.LandingRate = LandingRate.Hard; - le.ScoreDelta -= 20; + le.ScoreDelta = -25; } else if (landingData.VerticalSpeed < -350) { le.LandingRate = LandingRate.Aceeptable; - le.ScoreDelta -= 10; + le.ScoreDelta = -10; } else if (landingData.VerticalSpeed < -275) { @@ -45,12 +45,12 @@ private void ProcessLandingData(AircraftFlightData landingData) else if (landingData.VerticalSpeed < -175) { le.LandingRate = LandingRate.Perfect; - le.ScoreDelta += 20; + le.ScoreDelta = 10; } else if (landingData.VerticalSpeed < -125) { le.LandingRate = LandingRate.Soft; - le.ScoreDelta -= 10; + le.ScoreDelta = -10; } // TODO future handling of pitch @@ -67,9 +67,9 @@ public override void ProcessFlightData(AircraftFlightData Data) return; } - if (Data.GroundVelocity < 35) + if (Data.GroundVelocity < 35 && Data.MaxThorttlePosition() < 50) { - TaxiInEvent ti = new TaxiInEvent() + var ti = new TaxiInEvent() { FuelWeightLbs = Data.FuelWeightLbs }; diff --git a/FSTRaK/Models/FlightManager/State/TakeoffRollState.cs b/FSTRaK/Models/FlightManager/State/TakeoffRollState.cs index da9fd93..88deea2 100644 --- a/FSTRaK/Models/FlightManager/State/TakeoffRollState.cs +++ b/FSTRaK/Models/FlightManager/State/TakeoffRollState.cs @@ -12,7 +12,7 @@ internal class TakeoffRollState : AbstractState public TakeoffRollState (FlightManager Context) : base(Context) { - this._eventInterval = 2000; + this._eventInterval = 5000; this.Name = "Takeoff Roll"; this.IsMovementState = true; } @@ -21,7 +21,7 @@ public override void ProcessFlightData(AircraftFlightData Data) if (!Convert.ToBoolean(Data.SimOnGround)) { - TakeoffEvent To = new TakeoffEvent() { FlapsPosition = Data.FlapPosition, FuelWeightLbs = Data.FuelWeightLbs }; + var To = new TakeoffEvent() { FlapsPosition = Data.FlapPosition, FuelWeightLbs = Data.FuelWeightLbs }; AddFlightEvent(Data, To); Context.State = new FlightState(Context); return; diff --git a/FSTRaK/Models/FlightManager/State/TaxiInState.cs b/FSTRaK/Models/FlightManager/State/TaxiInState.cs index d468991..f0285be 100644 --- a/FSTRaK/Models/FlightManager/State/TaxiInState.cs +++ b/FSTRaK/Models/FlightManager/State/TaxiInState.cs @@ -11,13 +11,13 @@ internal class TaxiInState : AbstractState public TaxiInState(FlightManager Context) : base(Context) { - this._eventInterval = 5000; + this._eventInterval = 10000; this.Name = "Taxi In"; this.IsMovementState = true; } public override void ProcessFlightData(AircraftFlightData Data) { - if (Data.GroundVelocity > 35) + if ((Data.GroundVelocity > 40 && Data.MinThrottlePosition() > 75) || Data.SimOnGround != 1) { Context.State = new TakeoffRollState(Context); return; diff --git a/FSTRaK/Models/FlightManager/State/TaxiOutState.cs b/FSTRaK/Models/FlightManager/State/TaxiOutState.cs index cbd5f24..f458e3b 100644 --- a/FSTRaK/Models/FlightManager/State/TaxiOutState.cs +++ b/FSTRaK/Models/FlightManager/State/TaxiOutState.cs @@ -11,13 +11,13 @@ internal class TaxiOutState : AbstractState public TaxiOutState(FlightManager Context) : base(Context) { - this._eventInterval = 5000; + this._eventInterval = 10000; this.Name = "Taxi Out"; this.IsMovementState = true; } public override void ProcessFlightData(AircraftFlightData Data) { - if(Data.GroundVelocity > 35) + if ((Data.GroundVelocity > 40 && Data.MinThrottlePosition() > 75) || Data.SimOnGround != 1) { Context.State = new TakeoffRollState(Context); return; diff --git a/FSTRaK/Models/SimConnectService/SimConnectService.cs b/FSTRaK/Models/SimConnectService/SimConnectService.cs index 8b383c1..b013b8d 100644 --- a/FSTRaK/Models/SimConnectService/SimConnectService.cs +++ b/FSTRaK/Models/SimConnectService/SimConnectService.cs @@ -32,7 +32,7 @@ internal sealed class SimConnectService : INotifyPropertyChanged private bool _isConnected = false; public bool IsConnected { - get => _isConnected; + get => _isConnected; private set { if (value != _isConnected) @@ -46,10 +46,10 @@ private set private bool _isInFlight = false; public bool IsInFlight { - get => _isInFlight; + get => _isInFlight; private set { - if(value != _isInFlight) + if (value != _isInFlight) { _isInFlight = value; OnPropertyChanged(nameof(IsInFlight)); @@ -57,7 +57,7 @@ private set } } } - + // PAUSE_STATE_FLAG_OFF 0 // PAUSE_STATE_FLAG_PAUSE 1 // "full" Pause (sim + traffic + etc...) // PAUSE_STATE_FLAG_PAUSE_WITH_SOUND 2 // FSX Legacy Pause (not used anymore) @@ -83,7 +83,7 @@ public bool IsCrashed get => _isCrashed; private set { - if(value != _isCrashed) + if (value != _isCrashed) { _isCrashed = value; OnPropertyChanged(nameof(IsCrashed)); @@ -105,6 +105,19 @@ private set } } + private string _loadedAircraft = string.Empty; + public string LoadedAircraft + { + get => _loadedAircraft; + private set + { + if (value != _loadedAircraft) + { + _loadedAircraft = value; + OnPropertyChanged(); + } + } + } private AircraftFlightData _flightData; @@ -121,7 +134,7 @@ private set } } - private double _nearestAirportDistance = Double.MaxValue; + public double NearestAirportDistance { get; set; } = Double.MaxValue; private string _nearestAirport = string.Empty; public string NearestAirport { @@ -162,7 +175,7 @@ public static SimConnectService Instance internal void Initialize() { // Create a handle and hook to recieve windows messages - WindowInteropHelper lWih = new WindowInteropHelper(System.Windows.Application.Current.MainWindow); + var lWih = new WindowInteropHelper(System.Windows.Application.Current.MainWindow); lHwnd = lWih.Handle; gHs = HwndSource.FromHwnd(lHwnd); gHs.AddHook(new HwndSourceHook(WndProc)); @@ -191,7 +204,7 @@ private void ConnectToSimulator() { try { - Log.Debug("Trying to connect"); + Log.Information("Trying to connect to the simulator..."); _simconnect = new SimConnect("FSTrAk", lHwnd, WM_USER_SIMCONNECT, null, 0); if (_simconnect != null) { @@ -200,7 +213,7 @@ private void ConnectToSimulator() } catch (COMException ex) { - Log.Debug(ex.Message); + Log.Debug(ex, ex.Message); // Do nothing } } @@ -257,12 +270,19 @@ private void ConfigureSimconnect() _simconnect.AddToDataDefinition(DataDefinitions.FlightData, "GENERAL ENG PCT MAX RPM:3", "percent", SIMCONNECT_DATATYPE.FLOAT64, 0.0f, SimConnect.SIMCONNECT_UNUSED); _simconnect.AddToDataDefinition(DataDefinitions.FlightData, "GENERAL ENG PCT MAX RPM:4", "percent", SIMCONNECT_DATATYPE.FLOAT64, 0.0f, SimConnect.SIMCONNECT_UNUSED); + _simconnect.AddToDataDefinition(DataDefinitions.FlightData, "GENERAL ENG THROTTLE LEVER POSITION:1", "percent", SIMCONNECT_DATATYPE.FLOAT64, 0.0f, SimConnect.SIMCONNECT_UNUSED); + _simconnect.AddToDataDefinition(DataDefinitions.FlightData, "GENERAL ENG THROTTLE LEVER POSITION:2", "percent", SIMCONNECT_DATATYPE.FLOAT64, 0.0f, SimConnect.SIMCONNECT_UNUSED); + _simconnect.AddToDataDefinition(DataDefinitions.FlightData, "GENERAL ENG THROTTLE LEVER POSITION:3", "percent", SIMCONNECT_DATATYPE.FLOAT64, 0.0f, SimConnect.SIMCONNECT_UNUSED); + _simconnect.AddToDataDefinition(DataDefinitions.FlightData, "GENERAL ENG THROTTLE LEVER POSITION:4", "percent", SIMCONNECT_DATATYPE.FLOAT64, 0.0f, SimConnect.SIMCONNECT_UNUSED); + _simconnect.RegisterDataDefineStruct(DataDefinitions.FlightData); // Subscribe to System events - _simconnect.SubscribeToSystemEvent(EVENTS.FLIGHT_LOADED, "FlightLoaded"); - _simconnect.SubscribeToSystemEvent(EVENTS.PAUSE, "Pause_EX1"); - _simconnect.SubscribeToSystemEvent(EVENTS.CRASHED, "Crashed"); + _simconnect.SubscribeToSystemEvent(EVENTS.FlightLoaded, "FlightLoaded"); + _simconnect.SubscribeToSystemEvent(EVENTS.Pause, "Pause_EX1"); + _simconnect.SubscribeToSystemEvent(EVENTS.Crashed, "Crashed"); + _simconnect.SubscribeToSystemEvent(EVENTS.AircraftLoaded, "AircraftLoaded"); + // Register listeners on simconnect events @@ -289,23 +309,35 @@ private void Simconnect_OnRecvSystemState(SimConnect sender, SIMCONNECT_RECV_SYS private void Simconnect_OnRecvFilename(SimConnect sender, SIMCONNECT_RECV_EVENT_FILENAME data) { - LoadedFlight = data.szFileName; - Log.Debug(LoadedFlight); + if (data.uEventID == (uint)EVENTS.FlightLoaded) + { + LoadedFlight = data.szFileName; + Log.Debug(LoadedFlight); + }; + if (data.uEventID == (uint)EVENTS.AircraftLoaded) + { + LoadedAircraft = data.szFileName; + Log.Debug(data.szFileName); + }; + } private void Simconnect_OnRecvEvent(SimConnect sender, SIMCONNECT_RECV_EVENT data) { switch (data.uEventID) { - case (int)EVENTS.FLIGHT_LOADED: + case (int)EVENTS.FlightLoaded: // Do nothing, this is handled in OnRecvFileName break; - case (int)EVENTS.PAUSE: + case (int)EVENTS.Pause: PauseState = data.dwData; break; - case (int)EVENTS.CRASHED: + case (int)EVENTS.Crashed: IsCrashed = true; break; + case (int)EVENTS.AircraftLoaded: + // Do nothing, this is handled in OnRecvFileName; + break; } } @@ -331,14 +363,14 @@ private void simconnect_OnRecvQuit(SimConnect sender, SIMCONNECT_RECV data) void simconnect_OnRecvOpen(SimConnect sender, SIMCONNECT_RECV_OPEN data) { - Log.Information("Sim connection Openned!"); + Log.Information("Sim connection success!"); _connectionTimer.Stop(); IsConnected = true; } void simconnect_OnRecvException(SimConnect sender, SIMCONNECT_RECV_EXCEPTION data) { - Log.Error(data.dwException.ToString()); + Log.Error($"Simconnect excpetion {data.dwException}"); } private void Simconnect_OnRecvSimobjectData(SimConnect sender, SIMCONNECT_RECV_SIMOBJECT_DATA data) @@ -349,7 +381,7 @@ private void Simconnect_OnRecvSimobjectData(SimConnect sender, SIMCONNECT_RECV_S public void RequestNearestAirport() { - _nearestAirportDistance = Double.MaxValue; + NearestAirportDistance = Double.MaxValue; _simconnect.RequestFacilitiesList_EX1(SIMCONNECT_FACILITY_LIST_TYPE.AIRPORT, Requests.NearbyAirportsRequest); } @@ -361,24 +393,24 @@ private void Simconnect_OnRecvAirportList(SimConnect sender, SIMCONNECT_RECV_AIR if (myCoordinates != null) { - foreach (SIMCONNECT_DATA_FACILITY_AIRPORT a in data.rgData.Cast()) + foreach (var a in data.rgData.Cast()) { var airportCoord = new GeoCoordinate(a.Latitude, a.Longitude); var distance = airportCoord.GetDistanceTo(myCoordinates); - if (distance < _nearestAirportDistance) + if (distance < NearestAirportDistance) { if(a.Icao != NearestAirport) { NearestAirport = a.Icao; - _nearestAirportDistance = distance; - Log.Information($"Closest found airport is {NearestAirport} at {_nearestAirportDistance} meters!"); + NearestAirportDistance = distance; + Log.Information($"Closest found airport is {NearestAirport} at {NearestAirportDistance} meters!"); } } } } } catch (Exception ex) { - Log.Error(ex.Message); + Log.Error(ex, ex.Message); } } diff --git a/FSTRaK/Properties/DataSources/FSTRaK.Models.FlightEvent.datasource b/FSTRaK/Properties/DataSources/FSTRaK.Models.FlightEvent.datasource deleted file mode 100644 index 3ecf208..0000000 --- a/FSTRaK/Properties/DataSources/FSTRaK.Models.FlightEvent.datasource +++ /dev/null @@ -1,10 +0,0 @@ - - - - FSTRaK.Models.FlightEvent, FSTRaK, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null - \ No newline at end of file diff --git a/FSTRaK/Resources/ButtonsTheme.xaml b/FSTRaK/Resources/ButtonsTheme.xaml index fb160c2..8e992bf 100644 --- a/FSTRaK/Resources/ButtonsTheme.xaml +++ b/FSTRaK/Resources/ButtonsTheme.xaml @@ -1,7 +1,9 @@  + + + + + + + \ No newline at end of file diff --git a/FSTRaK/Utils/FlightExtentionMethods.cs b/FSTRaK/Utils/FlightExtentionMethods.cs new file mode 100644 index 0000000..902f4d0 --- /dev/null +++ b/FSTRaK/Utils/FlightExtentionMethods.cs @@ -0,0 +1,36 @@ +using FSTRaK.Models; +using FSTRaK.Models.Entity; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FSTRaK.Utils +{ + internal static class FlightExtentionMethods + { + public static Airport DepartureAirportDetails(this Flight flight) + { + var airport = AirportResolver.Instance.AirportsDictionary[flight.DepartureAirport]; + if (airport == null) + return airport; + return new Airport + { + icao = flight.DepartureAirport + }; + } + + public static Airport ArrivalAirportDetails(this Flight flight) + { + var airport = AirportResolver.Instance.AirportsDictionary[flight.ArrivalAirport]; + if (airport == null) + return airport; + return new Airport + { + icao = flight.ArrivalAirport + }; + } + + } +} diff --git a/FSTRaK/Utils/ResourceUtils.cs b/FSTRaK/Utils/ResourceUtils.cs new file mode 100644 index 0000000..e73ffe9 --- /dev/null +++ b/FSTRaK/Utils/ResourceUtils.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; + +namespace FSTRaK.Utils +{ + internal static class ResourceUtils + { + public static System.Drawing.Color GetColorFromResource(string name) + { + var mColor = (System.Windows.Media.Color)Application.Current.Resources[name]; + return System.Drawing.Color.FromArgb(mColor.A, mColor.R, mColor.G, mColor.B); + + } + } +} diff --git a/FSTRaK/ViewModels/BaseViewModel.cs b/FSTRaK/ViewModels/BaseViewModel.cs index 04634dd..4c37c29 100644 --- a/FSTRaK/ViewModels/BaseViewModel.cs +++ b/FSTRaK/ViewModels/BaseViewModel.cs @@ -1,5 +1,4 @@ -using Serilog; -using System.ComponentModel; +using System.ComponentModel; using System.Runtime.CompilerServices; namespace FSTRaK.ViewModels diff --git a/FSTRaK/ViewModels/FlightDetailsViewModel.cs b/FSTRaK/ViewModels/FlightDetailsViewModel.cs index 68738de..56c803d 100644 --- a/FSTRaK/ViewModels/FlightDetailsViewModel.cs +++ b/FSTRaK/ViewModels/FlightDetailsViewModel.cs @@ -5,8 +5,9 @@ using MapControl; using Serilog; using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; -using System.Text; using System.Windows; namespace FSTRaK.ViewModels @@ -14,126 +15,145 @@ namespace FSTRaK.ViewModels internal class FlightDetailsViewModel : BaseViewModel { private Flight _flight; - StringBuilder _sb; public Flight Flight { - get - { - return _flight; - } - set + get => _flight; + set { - if (_flight != value) + if (_flight == value) return; + _flight = value; + FlightPath = new ObservableCollection(_flight.FlightEvents + .OrderBy(e => e.ID) + .Select(e => new Location(e.Latitude, e.Longitude))); + + double minLon = Double.MaxValue, minLat = Double.MaxValue, maxLon = Double.MinValue, maxLat = Double.MinValue; + + FlightPath.ToList().ForEach(coords => { - - _flight = value; - FlightPath = new LocationCollection(_flight.FlightEvents.Select(e => new Location(e.Latitude, e.Longitude))); - - _sb.Clear() - .AppendLine($"Departed From: {_flight.DepartureAirport}"); - - if(_flight.FlightOutcome == FlightOutcome.Crashed) - { - _sb.AppendLine(($"Crashed Near {_flight.ArrivalAirport}")); - } else - { - _sb.AppendLine(($"Arrived At: {_flight.ArrivalAirport}")); - } + minLon = Math.Min(minLon, coords.Longitude); + maxLon = Math.Max(maxLon, coords.Longitude); + minLat = Math.Min(minLat, coords.Latitude); + maxLat = Math.Max(maxLat, coords.Latitude); + }); - _sb.AppendLine($"Start Time: {_flight.StartTime}") - .AppendLine($"End Time: {_flight.EndTime}") - .AppendLine($"Block Time: {_flight.FlightTime}") - .AppendLine($"Fuel Used: {TotalFuelUsed}") - .AppendLine($"Flown Distance VSI: {FlightDistance}") - .AppendLine($"Landing VSI: {LandingVerticalSpeed}") - .AppendLine($"Score: {_flight.Score}") - .ToString(); + var boundingBox = new BoundingBox(minLat, minLon, maxLat, maxLon); + Log.Debug($"{boundingBox.Center} {boundingBox.Width}"); + ViewPort = boundingBox; - FlightParams = _sb.ToString(); + ScoreboardText = _flight.GetScoreDetails(); - double minLon = Double.MaxValue, minLat = Double.MaxValue, maxLon = Double.MinValue, maxLat = Double.MinValue; + FlightParams = _flight.ToString(); - FlightPath.ForEach(coords => - { - minLon = Math.Min(minLon, coords.Longitude); - maxLon = Math.Max(maxLon, coords.Longitude); - minLat = Math.Min(minLat, coords.Latitude); - maxLat = Math.Max(maxLat, coords.Latitude); + GeneratePushpins(); - }); + OnPropertyChanged(); + OnPropertyChanged(nameof(FlightPath)); + OnPropertyChanged(nameof(AltSpeedGroundAltDictionary)); + } + } - var boundingBox = new BoundingBox(minLat, minLon, maxLat, maxLon); - Log.Debug($"{boundingBox.Center} {boundingBox.Width}"); - ViewPort = boundingBox; + private void GeneratePushpins() + { + var markerEvents = _flight.FlightEvents.Where(e => e is ScoringEvent || e is TakeoffEvent).ToList(); + + // Clear adjacent landings + var landings = markerEvents.Where(e => e is LandingEvent).ToList(); + for (var i = 0; i < landings.Count; i++) + { + if (i <= 0) continue; + if (landings[i].Time < landings[i - 1].Time.AddSeconds(10)) + { + markerEvents.Remove(landings[i]); + } + } + + MarkerList.Clear(); + foreach (var e in markerEvents) + { + var pin = new FlightEventPushpin(); + if(e is ScoringEvent @event) + { + if (@event.ScoreDelta < -15) + pin.Color = "Red"; + else if (@event.ScoreDelta < 0) + pin.Color = "Yellow"; - OnPropertyChanged(); - OnPropertyChanged(nameof(FlightPath)); } - } + pin.Text = e.ToString(); + pin.Location = $"{e.Latitude},{e.Longitude}"; + MarkerList.Add(pin); + } } + public ObservableCollection FlightPath { get; private set; } + + private string _flightParams; public string FlightParams { - get { return _flightParams; } - set + get => _flightParams; + private set { _flightParams = value; OnPropertyChanged(); } } + private ObservableCollection _markerList = new ObservableCollection(); - public string LandingVerticalSpeed + public ObservableCollection MarkerList { - get + get => _markerList; + set { - if(_flight != null) - { - var landingEvent = (LandingEvent)_flight.FlightEvents.FirstOrDefault(e => e is LandingEvent); - if (landingEvent != null) - { - return $"{landingEvent.VerticalSpeed:F0} ft/m"; - } - } - - return ""; + + _markerList = value; + OnPropertyChanged(); } } - public string FlightDistance + public Dictionary AltSpeedGroundAltDictionary { - get + get { - if (_flight != null) + // Building a dictionary where keys are the timestamp and values are arrays of ground speed altitude and ground altitude. + var altSpeedGroundDictionary = new Dictionary(); + var movementTime = _flight.FlightEvents.FirstOrDefault(e => e is TaxiOutEvent); + if (movementTime == null) return altSpeedGroundDictionary; { - var distanceInNM = _flight.FlightDistanceInMeters * Consts.MetersToNauticalMiles; - return $"{distanceInNM:F2} NM"; + var dataPoints = _flight.FlightEvents + .Where(e => e.Time > movementTime.Time) + .OrderBy(e => e.Time) + .GroupBy(e => (e.Time - new DateTime(1970, 1, 1)) + .TotalMilliseconds) + .Select(g => g.First()); + foreach (var e in dataPoints) + { + var altSpeedGroundArray = new double[] { e.Altitude, e.GroundSpeed, e.GroundAltitude }; + altSpeedGroundDictionary.Add(e.Time.ToOADate(), altSpeedGroundArray); + } } - return ""; + return altSpeedGroundDictionary; } } + + public string TotalFuelUsed { get { - if (_flight != null) - { - var totalFuelUsed = _flight.TotalFuelUsed; - var units = "Lbs"; + if (_flight == null) return ""; + var totalFuelUsed = _flight.TotalFuelUsed; + var units = "Lbs"; - if(Properties.Settings.Default.Units.Equals((int)Units.Metric)) - { - totalFuelUsed = totalFuelUsed * DataTypes.Consts.LbsToKgs; - units = "Kg"; - } + if (!Properties.Settings.Default.Units.Equals((int)Units.Metric)) return $"{totalFuelUsed:F2} {units}"; + totalFuelUsed *= DataTypes.Consts.LbsToKgs; + units = "Kg"; - return $"{totalFuelUsed:F2} {units}"; - } + return $"{totalFuelUsed:F2} {units}"; - return ""; } } @@ -142,7 +162,7 @@ public MapTileLayerBase MapProvider { get { - string resoueceKey = Properties.Settings.Default.MapTileProvider; + var resoueceKey = Properties.Settings.Default.MapTileProvider; var resource = Application.Current.Resources[resoueceKey] as MapTileLayerBase; if (resource != null) { @@ -155,7 +175,7 @@ public MapTileLayerBase MapProvider private BoundingBox _viewPort; public BoundingBox ViewPort { - get { return _viewPort; } + get => _viewPort; set { if (_viewPort != value) @@ -166,10 +186,66 @@ public BoundingBox ViewPort { } } - public LocationCollection FlightPath { get; private set; } - public FlightDetailsViewModel() + private bool _isShowPath = true; + public bool IsShowPath { get => _isShowPath; + set + { + _isShowPath = value; + OnPropertyChanged(); + } + } + + private bool _isShowFlightDetails = true; + public bool IsShowFlightDetails + { + get => _isShowFlightDetails; + set + { + _isShowFlightDetails = value; + OnPropertyChanged(); + } + } + + private bool _isShowAltSpeedCharts = false; + public bool IsShowAltSpeedCharts { - _sb = new StringBuilder(); + get => _isShowAltSpeedCharts; + set + { + _isShowAltSpeedCharts = value; + OnPropertyChanged(); + } + } + + private bool _isShowScoreboard = false; + public bool IsShowScoreboard + { + get => _isShowScoreboard; + set + { + _isShowScoreboard = value; + OnPropertyChanged(); + } + } + + private string scoreboardText; + + public string ScoreboardText + { + get => scoreboardText; + set { + scoreboardText = value; + OnPropertyChanged(); + } + } + + public class FlightEventPushpin + { + public string Location { get; set; } + public string Text { get; set; } = string.Empty; + + public string Color { get; set; } = "Green"; + } } } diff --git a/FSTRaK/ViewModels/LiveViewViewModel.cs b/FSTRaK/ViewModels/LiveViewViewModel.cs index 7e94ac1..4ba17a1 100644 --- a/FSTRaK/ViewModels/LiveViewViewModel.cs +++ b/FSTRaK/ViewModels/LiveViewViewModel.cs @@ -6,6 +6,7 @@ using System.Collections.ObjectModel; using System.ComponentModel; using System.Linq; +using System.Text; using System.Windows; namespace FSTRaK.ViewModels @@ -27,50 +28,6 @@ public Flight ActiveFlight } } - private string _title; - public string Title - { - get { return _title; } - set - { - _title = value; - OnPropertyChanged(); - } - } - - private string _model; - public string Model - { - get { return _model; } - set - { - _model = value; - OnPropertyChanged(); - } - } - - private string _airline; - public string Airline - { - get { return _airline; } - set - { - _airline = value; - OnPropertyChanged(); - } - } - - private string _type; - public string Type - { - get { return _type; } - set - { - _type = value; - OnPropertyChanged(); - } - } - private bool _isShowAirplane; public bool IsShowAirplane @@ -204,7 +161,7 @@ public MapTileLayerBase MapProvider { get { - string resoueceKey = Properties.Settings.Default.MapTileProvider; + var resoueceKey = Properties.Settings.Default.MapTileProvider; var resource = Application.Current.Resources[resoueceKey] as MapTileLayerBase; if (resource != null) { @@ -254,15 +211,6 @@ private void SimconnectManagerUpdate(object sender, PropertyChangedEventArgs e) } - // Begining of flight - if (_flightManager.State is FlightStartedState && ActiveFlight != null) - { - Title = $"Aircraft: {ActiveFlight.Aircraft.Title}"; - Model = $"Model: {ActiveFlight.Aircraft.Model}"; - Type = $"Type: {ActiveFlight.Aircraft.AircraftType}"; - Airline = $"Airline: {ActiveFlight.Aircraft.Airline}"; - } - OnPropertyChanged(nameof(_flightManager.ActiveFlight)); OnPropertyChanged(nameof(Location)); break; diff --git a/FSTRaK/ViewModels/LogbookViewModel.cs b/FSTRaK/ViewModels/LogbookViewModel.cs index 2548b78..8ce6b4c 100644 --- a/FSTRaK/ViewModels/LogbookViewModel.cs +++ b/FSTRaK/ViewModels/LogbookViewModel.cs @@ -6,15 +6,19 @@ using System; using System.Collections.ObjectModel; using System.Data.Entity; +using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; +using System.Timers; namespace FSTRaK.ViewModels { internal class LogbookViewModel : BaseViewModel { FlightManager _flightManager = FlightManager.Instance; + + private System.Timers.Timer _typingTimer; public RelayCommand OnLoogbookLoadedCommand { get; set; } public RelayCommand DeleteFlightCommand { get; set; } @@ -51,30 +55,36 @@ public Flight SelectedFlight { get } } - public LocationCollection FlightPath = new LocationCollection(); - - public LogbookViewModel() { Flights = new ObservableCollection(); _flightDetailsViewModel = new FlightDetailsViewModel(); + _typingTimer = new System.Timers.Timer(500); - _flightManager.PropertyChanged += (async (s,e) => + _flightManager.PropertyChanged += async (s,e) => { if(e.PropertyName.Equals(nameof(_flightManager.State)) && (_flightManager.State is FlightEndedState)) { using (var logbookContext = new LogbookContext()) { - await LoadFlights(500); - var latestId = logbookContext.Flights.Max(f => f.ID); - SelectedFlight = logbookContext.Flights - .Where(f => f.ID == latestId) - .Include(f => f.Aircraft) - .Include(f => f.FlightEvents) - .SingleOrDefault(); + try + { + await LoadFlights(500); + var latestId = logbookContext.Flights.Max(f => f.ID); + SelectedFlight = logbookContext.Flights + .Where(f => f.ID == latestId) + .Include(f => f.Aircraft) + .Include(f => f.FlightEvents) + .SingleOrDefault(); + } + catch (Exception ex) + { + Log.Error(ex, ex.Message); + } + } } - }); + }; OnLoogbookLoadedCommand = new RelayCommand(o => { @@ -96,14 +106,36 @@ public LogbookViewModel() } catch (Exception ex) { - Log.Debug(ex.ToString()); + Log.Debug(ex, ex.Message); } } }); }); + + _typingTimer.Elapsed += _typingTimer_Elapsed; + } + + private void _typingTimer_Elapsed(object sender, ElapsedEventArgs e) + { + SearchFlights(); + _typingTimer.Stop(); } + private string _searchText; + public string SearchText + { + get { return _searchText; } + set + { + _typingTimer.Stop(); + _typingTimer.Start(); + _searchText = value; + // Actual search is in the typingTimerElapsed event handler. + } + } + + private Task LoadFlights() { return LoadFlights(0); @@ -130,9 +162,41 @@ private Task LoadFlights(int delay) } catch (Exception ex) { - Log.Debug(ex.Message); - Log.Debug(ex.ToString()); - Log.Debug(ex.InnerException.ToString()); + Log.Error(ex, "Unhandled error occured!"); + } + } + }); + } + + private Task SearchFlights() + { + if(SearchText == null || SearchText.Equals(string.Empty)) + return LoadFlights(); + + return Task.Run(() => { + using (var logbookContext = new LogbookContext()) + { + try + { + var flights = logbookContext.Flights + .Where(f => + f.DepartureAirport.ToLower().Equals(SearchText.ToLower()) + || f.ArrivalAirport.ToLower().Equals(SearchText.ToLower()) + || f.Aircraft.Title.ToLower().Contains(SearchText.ToLower()) + || f.Aircraft.Model.ToLower().Contains(SearchText.ToLower()) + ) + .Include(f => f.Aircraft) + .Include(f => f.FlightEvents); + + App.Current.Dispatcher.Invoke((Action)delegate + { + Flights = new ObservableCollection(flights); + OnPropertyChanged(nameof(Flights)); + }); + } + catch (Exception ex) + { + Log.Error(ex, "Exception fetching Flights!"); } } }); diff --git a/FSTRaK/ViewModels/SettingsViewModel.cs b/FSTRaK/ViewModels/SettingsViewModel.cs index 4afe520..054cabd 100644 --- a/FSTRaK/ViewModels/SettingsViewModel.cs +++ b/FSTRaK/ViewModels/SettingsViewModel.cs @@ -117,7 +117,6 @@ public bool IsSaveOnlyCompleteFlights } set { - Log.Debug($"{value}"); _isSaveOnlyCompleteFlights = value; Properties.Settings.Default.IsSaveOnlyCompleteFlights = _isSaveOnlyCompleteFlights; OnPropertyChanged(); @@ -126,9 +125,9 @@ public bool IsSaveOnlyCompleteFlights public SettingsViewModel() : base() { - ResourceDictionary mapProviders = new ResourceDictionary(); + var mapProviders = new ResourceDictionary(); mapProviders.Source = new System.Uri("pack://application:,,,/Resources/MapProvidersDictionary.xaml", uriKind: System.UriKind.Absolute); - ObservableCollection layers = new ObservableCollection(); + var layers = new ObservableCollection(); foreach (DictionaryEntry provider in mapProviders) { @@ -150,10 +149,5 @@ public void OnLoaded() Units = (Units)Properties.Settings.Default.Units; } - - protected void OnPropertyChanged2([CallerMemberName] string name = null) - { - Log.Debug(name); - } } } diff --git a/FSTRaK/Views/FlightDetailsView.xaml b/FSTRaK/Views/FlightDetailsView.xaml index 0c8ed4d..b76e572 100644 --- a/FSTRaK/Views/FlightDetailsView.xaml +++ b/FSTRaK/Views/FlightDetailsView.xaml @@ -3,24 +3,41 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:viewmodels="clr-namespace:FSTRaK.ViewModels" + xmlns:viewmodels="clr-namespace:FSTRaK.ViewModels" xmlns:views="clr-namespace:FSTRaK.Views" - xmlns:map="clr-namespace:MapControl;assembly=MapControl.WPF" d:DataContext="{d:DesignInstance Type=viewmodels:FlightDetailsViewModel}" - d:DesignHeight="450" d:DesignWidth="800" + xmlns:map="clr-namespace:MapControl;assembly=MapControl.WPF" + d:DataContext="{d:DesignInstance Type=viewmodels:FlightDetailsViewModel}" + d:DesignHeight="600" d:DesignWidth="800" mc:Ignorable="d" Loaded="OnLoaded" Unloaded="OnUnLoaded" > - - - + + + @@ -30,24 +47,51 @@ ZoomLevel="3" Center="51,0" MapProjection="{StaticResource WebMercatorProjection}" - Margin="10,10,10,10"> + Margin="10,10,10,10" + > - - - - - + + + + + + + + + - - + + + + + + + + + + + + + + + - + diff --git a/FSTRaK/Views/FlightDetailsView.xaml.cs b/FSTRaK/Views/FlightDetailsView.xaml.cs index 7b47f99..cec9144 100644 --- a/FSTRaK/Views/FlightDetailsView.xaml.cs +++ b/FSTRaK/Views/FlightDetailsView.xaml.cs @@ -1,9 +1,15 @@ using FSTRaK.ViewModels; using MapControl; +using ScottPlot; +using ScottPlot.Plottable; +using ScottPlot.Renderable; using System; using System.ComponentModel; +using System.Drawing; +using System.Linq; using System.Windows; using System.Windows.Controls; +using FSTRaK.Utils; namespace FSTRaK.Views { @@ -21,23 +27,97 @@ public FlightDetailsView() private void OnLoaded(object s, RoutedEventArgs e) { ((FlightDetailsViewModel)DataContext).PropertyChanged += DataModel_OnPropertyChange; + + var graphColor = ResourceUtils.GetColorFromResource("BorderLightColor"); + + AltSpeedChart.Plot.XAxis.DateTimeFormat(true); + AltSpeedChart.Plot.YAxis.Label("Altitude"); + AltSpeedChart.Plot.YAxis2.Label("Ground Speed"); + AltSpeedChart.Plot.YAxis2.Ticks(true); + + + var legend = AltSpeedChart.Plot.Legend(); + + legend.FontBold = true; + + legend.FontColor = graphColor; + + legend.FillColor = Color.Transparent; + legend.OutlineColor = graphColor; + AltSpeedChart.Plot.Style(ScottPlot.Style.Black); + AltSpeedChart.Plot.Style( + figureBackground: Color.Transparent, + dataBackground: Color.Transparent + ); + AltSpeedChart.Plot.XAxis.Color(graphColor); + AltSpeedChart.Plot.XAxis2.Color(graphColor); + + AltSpeedChart.Plot.YAxis.Color(graphColor); + AltSpeedChart.Plot.YAxis2.Color(graphColor); + + + } private void OnUnLoaded(object s, RoutedEventArgs e) { ((FlightDetailsViewModel)DataContext).PropertyChanged -= DataModel_OnPropertyChange; + } private void DataModel_OnPropertyChange(object sender, PropertyChangedEventArgs e) { - if (e.PropertyName == "ViewPort") + switch (e.PropertyName) { - var viewPort = ((FlightDetailsViewModel)DataContext).ViewPort; - if (viewPort != null) - { - ZoomToBounds(viewPort); - } + case "ViewPort": + var viewPort = ((FlightDetailsViewModel)DataContext).ViewPort; + if (viewPort != null) + { + ZoomToBounds(viewPort); + } + break; + case "AltSpeedGroundAltDictionary": + var altSpeedGroundSeries = ((FlightDetailsViewModel)DataContext).AltSpeedGroundAltDictionary; + + if (altSpeedGroundSeries != null) + { + var timeX = altSpeedGroundSeries.Keys.ToArray(); + var altY = altSpeedGroundSeries.Values.Select(v => v[0]).ToArray(); + var speedY = altSpeedGroundSeries.Values.Select(v => v[1]).ToArray(); + var groundAltY = altSpeedGroundSeries.Values.Select(v => v[2]).ToArray(); + AltSpeedChart.Plot.Clear(); + + var altPlot = AltSpeedChart.Plot.AddScatter(timeX, altY); + altPlot.Label ="Altitude"; + altPlot.Smooth = false; + altPlot.MarkerSize = 0; + altPlot.LineWidth = 2; + + var gAltPlot = AltSpeedChart.Plot.AddScatter(timeX, groundAltY); + gAltPlot.Label = "Ground Altitude"; + gAltPlot.Smooth = true; + gAltPlot.MarkerSize = 0; + gAltPlot.LineWidth = 2; + + var speedPlot = AltSpeedChart.Plot.AddScatter(timeX, speedY); + speedPlot.Label = "Ground Speed"; + speedPlot.Smooth = false; + speedPlot.YAxisIndex = 1; + speedPlot.Smooth = false; + speedPlot.MarkerSize = 0; + speedPlot.LineWidth = 2; + + + + var legend = AltSpeedChart.Plot.Legend(); + legend.Location = Alignment.UpperLeft; + + AltSpeedChart.Refresh(); + } + break; + + } } diff --git a/FSTRaK/Views/LiveView.xaml b/FSTRaK/Views/LiveView.xaml index 86cc0d8..9c4d572 100644 --- a/FSTRaK/Views/LiveView.xaml +++ b/FSTRaK/Views/LiveView.xaml @@ -55,7 +55,7 @@ - + diff --git a/FSTRaK/Views/LogbookView.xaml b/FSTRaK/Views/LogbookView.xaml index f095ef2..7d60da0 100644 --- a/FSTRaK/Views/LogbookView.xaml +++ b/FSTRaK/Views/LogbookView.xaml @@ -4,7 +4,6 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:viewmodels="clr-namespace:FSTRaK.ViewModels" - xmlns:entity="clr-namespace:FSTRaK.Models.Entity" xmlns:b="http://schemas.microsoft.com/xaml/behaviors" xmlns:views="clr-namespace:FSTRaK.Views" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800" @@ -21,66 +20,131 @@ + + - + - + - + + + + + + - + - - - - - + + + + + - - - - - - - - - - - - - - - + + + + + + + + - - + + + + + + + + + + + - - + + + + + + + + - - + + - - + + + + + + + - - - - - - - + / NM + + , + + + + + + + + diff --git a/FSTRaK/Views/LogbookView.xaml.cs b/FSTRaK/Views/LogbookView.xaml.cs index f533b0c..ef76491 100644 --- a/FSTRaK/Views/LogbookView.xaml.cs +++ b/FSTRaK/Views/LogbookView.xaml.cs @@ -1,4 +1,4 @@ -using System.Windows; +using System; using System.Windows.Controls; diff --git a/FSTRaK/Views/MainWindow.xaml b/FSTRaK/Views/MainWindow.xaml index e0c50b0..5512282 100644 --- a/FSTRaK/Views/MainWindow.xaml +++ b/FSTRaK/Views/MainWindow.xaml @@ -65,12 +65,14 @@ - + + + diff --git a/FSTRaK/Views/MainWindow.xaml.cs b/FSTRaK/Views/MainWindow.xaml.cs index 96b299c..ab10c71 100644 --- a/FSTRaK/Views/MainWindow.xaml.cs +++ b/FSTRaK/Views/MainWindow.xaml.cs @@ -1,6 +1,7 @@ using FSTRaK.Models.FlightManager; using Serilog; using System; +using System.IO; using System.Windows; using System.Windows.Input; @@ -16,13 +17,25 @@ public MainWindow() { _flightManager = FlightManager.Instance; InitializeComponent(); + + // Add to tray + var icon = new System.Windows.Forms.NotifyIcon(); + var iconStream = Application.GetResourceStream(new Uri("pack://application:,,,/Resources/Images/FSTrAk.ico")).Stream; + icon.Icon = new System.Drawing.Icon(iconStream); + icon.Visible = true; + icon.DoubleClick += + delegate (object sender, EventArgs args) + { + this.Show(); + this.WindowState = WindowState.Normal; + }; } private void OnLoad(object sender, RoutedEventArgs e) { _flightManager.Initialize(); - string bingApiKey = Properties.Settings.Default.BingApiKey; + var bingApiKey = Properties.Settings.Default.BingApiKey; MapControl.BingMapsTileLayer.ApiKey = bingApiKey; } @@ -31,17 +44,28 @@ private void ButtonClick_CloseApplication (object sender, RoutedEventArgs e) Close(); } + private void ButtonClick_MinimizeApplication(object sender, RoutedEventArgs e) + { + WindowState = WindowState.Minimized; + } + private void DragWindow(object sender, MouseButtonEventArgs e) { - Serilog.Log.Debug("mouse!"); - Log.Debug($"{sender.GetType()}"); DragMove(); } + protected override void OnStateChanged(EventArgs e) + { + if (WindowState == System.Windows.WindowState.Minimized) + this.Hide(); + + base.OnStateChanged(e); + } + private void Window_Deactivated(object sender, EventArgs e) { - Window window = (Window)sender; + var window = (Window)sender; window.Topmost = Properties.Settings.Default.IsAlwaysOnTop; } diff --git a/FSTRaK/Views/OverlayTextCardControl.xaml b/FSTRaK/Views/OverlayTextCardControl.xaml index b842fdf..7bfbaa6 100644 --- a/FSTRaK/Views/OverlayTextCardControl.xaml +++ b/FSTRaK/Views/OverlayTextCardControl.xaml @@ -32,7 +32,7 @@ />