internal static void Departing(this FlightContext context) { /* * First check plausible scenarios. The easiest to track is an aerotow. * * If not, wait until the launch is completed. */ if (context.Flight.LaunchMethod == LaunchMethods.None) { var departure = context.Flight.PositionUpdates .Where(q => q.Heading != 0 && !double.IsNaN(q.Heading)) .OrderBy(q => q.TimeStamp) .Take(5) .ToList(); if (departure.Count > 4) { context.Flight.DepartureHeading = Convert.ToInt16(departure.Average(q => q.Heading)); if (context.Flight.DepartureHeading == 0) { context.Flight.DepartureHeading = 360; } // Only start method recognition after the heading has been determined context.Flight.LaunchMethod = LaunchMethods.Unknown | LaunchMethods.Aerotow | LaunchMethods.Winch | LaunchMethods.Self; } else { return; } } if (context.Flight.DepartureTime != null && (context.CurrentPosition.TimeStamp - (context.Flight.PositionUpdates.FirstOrDefault(q => q.Speed > 30)?.TimeStamp ?? context.CurrentPosition.TimeStamp)).TotalSeconds < 10) { return; } // We can safely try to extract the correct path if (context.Flight.LaunchMethod.HasFlag(LaunchMethods.Unknown | LaunchMethods.Aerotow)) { var encounters = context.TowEncounter().ToList(); if (encounters.Count(q => q?.Type == EncounterType.Tug || q?.Type == EncounterType.Tow) > 1) { return; } var encounter = encounters.SingleOrDefault(q => q?.Type == EncounterType.Tug || q?.Type == EncounterType.Tow); if (encounter != null) { context.Flight.LaunchMethod = LaunchMethods.Aerotow | (encounter.Type == EncounterType.Tug ? LaunchMethods.OnTow : LaunchMethods.TowPlane ); context.Flight.Encounters.Add(encounter); context.StateMachine.Fire(FlightContext.Trigger.TrackAerotow); return; } else if (encounters.Any(q => q == null)) { return; } context.Flight.LaunchMethod &= ~LaunchMethods.Aerotow; } if (context.Flight.LaunchMethod.HasFlag(LaunchMethods.Unknown)) { var x = new DenseVector(context.Flight.PositionUpdates.Select(w => (w.TimeStamp - context.Flight.DepartureTime.Value).TotalSeconds).ToArray()); var y = new DenseVector(context.Flight.PositionUpdates.Select(w => w.Altitude).ToArray()); var interpolation = CubicSpline.InterpolateNatural(x, y); var r = new List <double>(); var r2 = new List <double>(); for (var i = 0; i < (context.CurrentPosition.TimeStamp - context.Flight.DepartureTime.Value).TotalSeconds; i++) { r.Add(interpolation.Differentiate(i)); r2.Add(interpolation.Differentiate2(i)); } // When the initial climb has completed if (interpolation.Differentiate((context.CurrentPosition.TimeStamp - context.Flight.DepartureTime.Value).TotalSeconds) < 0) { // Skip the first element because heading is 0 when in rest var averageHeading = context.Flight.PositionUpdates.Skip(1).Average(q => q.Heading); // ToDo: Add check to see whether there is another aircraft nearby if (context.Flight.PositionUpdates .Skip(1) .Where(q => interpolation.Differentiate((context.CurrentPosition.TimeStamp - context.Flight.DepartureTime.Value).TotalSeconds) > 0) .Select(q => Geo.GetHeadingError(averageHeading, q.Heading)) .Any(q => q > 20) || Geo.DistanceTo( context.Flight.PositionUpdates.First().Location, context.CurrentPosition.Location) > 3000) { context.Flight.LaunchMethod = LaunchMethods.Self; } else { context.Flight.LaunchMethod = LaunchMethods.Winch; } context.Flight.LaunchFinished = context.CurrentPosition.TimeStamp; context.InvokeOnLaunchCompletedEvent(); context.StateMachine.Fire(FlightContext.Trigger.LaunchCompleted); } } }
internal static void Stationary(this FlightContext context) { if (context.CurrentPosition == null) { return; } if (context.CurrentPosition.Speed > 30) { double groundElevation = 0; if (context.Options.NearbyRunwayAccessor != null) { groundElevation = context.Options.NearbyRunwayAccessor( context.CurrentPosition.Location, Constants.RunwayQueryRadius)? .OrderBy(q => q.Sides .Min(w => Geo.DistanceTo(w, context.CurrentPosition.Location)) ).FirstOrDefault() ?.Sides .Average(q => q.Z) ?? 0; } // Walk back to when the speed was 0 var start = context.Flight.PositionUpdates .Where(q => (q.Speed == 0 || double.IsNaN(q.Speed)) && (context.CurrentPosition.TimeStamp - q.TimeStamp).TotalSeconds < 30) .OrderByDescending(q => q.TimeStamp) .FirstOrDefault(); if (start == null && context.CurrentPosition.Altitude > (groundElevation + Constants.ArrivalHeight)) { // The flight was already in progress, or we could not find the starting point (trees in line of sight?) // Create an estimation about the departure time. Unless contact happens high in the sky context.Flight.DepartureInfoFound = false; context.InvokeOnRadarContactEvent(); context.StateMachine.Fire(FlightContext.Trigger.TrackMovements); return; } else if (start == null && context.CurrentPosition.Altitude <= (groundElevation + Constants.ArrivalHeight)) { // ToDo: Try to estimate the departure time context.Flight.DepartureTime = context.CurrentPosition.TimeStamp; context.Flight.DepartureLocation = context.CurrentPosition.Location; context.Flight.PositionUpdates .Where(q => q.TimeStamp < context.Flight.DepartureTime.Value) .ToList() .ForEach(q => context.Flight.PositionUpdates.Remove(q)); context.Flight.DepartureInfoFound = false; } else if (start != null) { context.Flight.DepartureTime = start.TimeStamp; context.Flight.DepartureLocation = context.CurrentPosition.Location; // Remove points not related to this flight context.Flight.PositionUpdates .Where(q => q.TimeStamp < context.Flight.DepartureTime.Value) .ToList() .ForEach(q => context.Flight.PositionUpdates.Remove(q)); context.Flight.DepartureInfoFound = false; } context.Flight.DepartureHeading = (short)context.CurrentPosition.Heading; context.InvokeOnTakeoffEvent(); context.StateMachine.Fire(FlightContext.Trigger.Depart); } }
internal static void Arriving(this FlightContext context) { /* * - Create an estimate for the arrival time * - When data shows a landing, use that data * - When no data is received anymore, use the estimation */ double groundElevation = 0; if (context.Options.NearbyRunwayAccessor != null) { groundElevation = context.Options.NearbyRunwayAccessor( context.CurrentPosition.Location, Constants.RunwayQueryRadius)? .OrderBy(q => q.Sides .Min(w => Geo.DistanceTo(w, context.CurrentPosition.Location)) ).FirstOrDefault() ?.Sides .Average(q => q.Z) ?? 0; } if (context.CurrentPosition.Altitude > (groundElevation + Constants.ArrivalHeight)) { context.Flight.ArrivalTime = null; context.Flight.ArrivalInfoFound = null; context.Flight.ArrivalHeading = 0; context.StateMachine.Fire(FlightContext.Trigger.LandingAborted); return; } var arrival = context.Flight.PositionUpdates .Where(q => q.Heading != 0 && !double.IsNaN(q.Heading)) .OrderByDescending(q => q.TimeStamp) .Take(5) .ToList(); if (!arrival.Any()) { return; } if (context.CurrentPosition.Speed == 0) { /* * If a flight has been in progress, end the flight. * * When the aircraft has been registered mid flight the departure * location is unknown, and so is the time. Therefore look at the * flag which is set to indicate whether the departure location has * been found. * * ToDo: Also check the vertical speed as it might be an indication * that the flight is still in progress! (Aerobatic stuff and so) */ context.Flight.ArrivalTime = context.CurrentPosition.TimeStamp; context.Flight.ArrivalInfoFound = true; context.Flight.ArrivalHeading = Convert.ToInt16(arrival.Average(q => q.Heading)); context.Flight.ArrivalLocation = arrival.First().Location; if (context.Flight.ArrivalHeading == 0) { context.Flight.ArrivalHeading = 360; } context.InvokeOnLandingEvent(); context.StateMachine.Fire(FlightContext.Trigger.Arrived); } else if (!(context.Flight.ArrivalInfoFound ?? true) && context.CurrentPosition.TimeStamp > context.Flight.ArrivalTime.Value.AddSeconds(Constants.ArrivalTimeout)) { // Our theory needs to be finalized context.InvokeOnLandingEvent(); context.StateMachine.Fire(FlightContext.Trigger.Arrived); } else { var previousPoint = context.Flight.PositionUpdates.LastOrDefault(); if (previousPoint == null) { return; } // Take the average climbrate over the last few points var climbrates = new List <double>(); var speeds = new List <double>(); for (var i = context.Flight.PositionUpdates.Count - 1; i > Math.Max(context.Flight.PositionUpdates.Count - 15, 0); i--) { var p1 = context.Flight.PositionUpdates[i]; var p2 = context.Flight.PositionUpdates[i - 1]; var deltaAltitude = p1.Altitude - p2.Altitude; var deltaTime = p1.TimeStamp - p2.TimeStamp; speeds.Add(p1.Speed); climbrates.Add(deltaAltitude / deltaTime.TotalSeconds); } if (!climbrates.Any()) { context.Flight.ArrivalTime = null; context.Flight.ArrivalInfoFound = null; context.Flight.ArrivalHeading = 0; context.Flight.ArrivalLocation = null; return; } var average = climbrates.Average(); double ETUA = context.CurrentPosition.Altitude / -average; if (double.IsInfinity(ETUA) || ETUA > (60 * 10) || ETUA < 0) { context.Flight.ArrivalTime = null; context.Flight.ArrivalInfoFound = null; context.Flight.ArrivalHeading = 0; context.Flight.ArrivalLocation = null; return; } var averageHeading = arrival.Average(q => q.Heading); context.Flight.ArrivalTime = context.CurrentPosition.TimeStamp.AddSeconds(ETUA); context.Flight.ArrivalInfoFound = false; context.Flight.ArrivalHeading = Convert.ToInt16(averageHeading); context.Flight.ArrivalLocation = context.CurrentPosition.Location.HaversineExtrapolation( averageHeading, speeds.Average() * 0.514444444 * ETUA); // Knots to m/s times the estimated time until arrival } }