Esempio n. 1
0
 private void SubscribeContextEventHandlers(FlightContext context)
 {
     // Subscribe to the events so we can propagate 'em via the factory
     context.OnTakeoff             += (sender, args) => OnTakeoff?.Invoke(sender, args);
     context.OnLaunchCompleted     += (sender, args) => OnLaunchCompleted?.Invoke(sender, args);
     context.OnLanding             += (sender, args) => OnLanding?.Invoke(sender, args);
     context.OnRadarContact        += (sender, args) => OnRadarContact?.Invoke(sender, args);
     context.OnCompletedWithErrors += (sender, args) => OnCompletedWithErrors?.Invoke(sender, args);
 }
Esempio n. 2
0
        internal static void Initialize(this FlightContext context)
        {
            context.LatestTimeStamp = DateTime.MinValue;

            context.Flight = new Flight
            {
                Aircraft = context.Options.AircraftId
            };

            context.StateMachine.Fire(FlightContext.Trigger.Next);
        }
        internal static AircraftRelation WhatAmI(this FlightContext context1, FlightContext context2)
        {
            var c2Position = context2.GetPositionAt(context1.CurrentPosition.TimeStamp);

            if (c2Position == null &&
                Math.Abs((context1.Flight.DepartureTime - context2.Flight.DepartureTime)?.TotalSeconds ?? 21) < 30)
            {
                return(AircraftRelation.Indeterministic);
            }
            else if (c2Position == null ||
                     context2.CurrentPosition == null ||
                     (context1.CurrentPosition.TimeStamp - context2.CurrentPosition.TimeStamp).TotalSeconds > 30 ||
                     context1.CurrentPosition.Location.DistanceTo(c2Position.Location) > 200)
            {
                // In this case we conclusively know there's nothing to be found
                return(AircraftRelation.None);
            }

            var bearings = new List <double>();

            for (var i = context1.Flight.PositionUpdates.Count - 1; i >= 0; i--)
            {
                var p1 = context1.Flight.PositionUpdates[i];
                var p2 = context2.GetPositionAt(p1.TimeStamp);

                // If this statement is true, the changes that our interpolation is right are getting more slim by the tick.
                if (p1.TimeStamp < context2.Flight.PositionUpdates[0].TimeStamp)
                {
                    break;
                }

                if (Math.Abs(p1.Speed - p2.Speed) > 20 ||
                    Math.Abs(p1.Altitude - p2.Altitude) > 100 ||
                    p1.Location.DistanceTo(p2.Location) > 200)
                {
                    return(AircraftRelation.None);
                }

                var angle = (p1.Location.DegreeBearing(p2.Location) - p1.Heading + 360) % 360;

                bearings.Add(angle);
            }

            var bearing = Geo.MeanAngle(bearings.ToArray());

            return(90 < bearing && bearing < 270
                ? AircraftRelation.Towplane
                : AircraftRelation.OnTow);
        }
Esempio n. 4
0
        public IEnumerable <FlightContext> FindNearby(FlightContext context, double distance = 0.2)
        {
            if (context == null)
            {
                throw new ArgumentException($"{nameof(context)} should not be null");
            }

            var nearbyPositions = _map.Nearby(new PositionUpdate(null, DateTime.MinValue, context.CurrentPosition.Location.Y, context.CurrentPosition.Location.X), distance);

            foreach (var position in nearbyPositions)
            {
                if (position.Aircraft != context.Options.AircraftId)
                {
                    yield return(_flightContextDictionary[position.Aircraft]);
                }
            }
        }
        internal static IEnumerable <Encounter> TowEncounter(this FlightContext context)
        {
            var nearbyAircraft = context.Options.NearbyAircraftAccessor?.Invoke(
                context.CurrentPosition.Location,
                0.5)
                                 .ToList();

            if (nearbyAircraft == null)
            {
                yield break;
            }

            foreach (var aircraft in nearbyAircraft)
            {
                var iAm = context.WhatAmI(aircraft);

                if (iAm == AircraftRelation.OnTow)
                {
                    yield return(new Encounter
                    {
                        Aircraft = aircraft.Options.AircraftId,
                        Start = aircraft.Flight.DepartureTime,
                        Type = EncounterType.Tug
                    });
                }
                else if (iAm == AircraftRelation.Towplane)
                {
                    yield return(new Encounter
                    {
                        Aircraft = aircraft.Options.AircraftId,
                        Start = aircraft.Flight.DepartureTime,
                        Type = EncounterType.Tow
                    });
                }
                else if (iAm == AircraftRelation.Indeterministic)
                {
                    yield return(null);
                }
            }
        }
Esempio n. 6
0
        internal static void TrackAerotow(this FlightContext context)
        {
            // ToDo: Think about the possibility of a paired landing.

            if (context.Options.AircraftAccessor == null)
            {
                throw new Exception($"Unable to track tow without {nameof(FlightContextFactory)}");
            }

            var target = context.Flight.Encounters
                         .FirstOrDefault(q => q.Type == Models.EncounterType.Tow ||
                                         q.Type == Models.EncounterType.Tug);

            if (target == null)
            {
                // Note of caution; this situation should ideally never happen. If it does it would be a design flaw in the state machine?
                context.StateMachine.Fire(FlightContext.Trigger.LaunchCompleted);
                return;
            }

            var otherContext = context.Options.AircraftAccessor(target.Aircraft);

            var iAm = context.WhatAmI(otherContext);

            if (iAm == null)
            {
                return;              // (I'm nothing. Try again next time)
            }
            if (iAm == Internal.Geo.AircraftRelation.None ||
                (target.Type == Models.EncounterType.Tow && iAm != Internal.Geo.AircraftRelation.Towplane) ||
                (target.Type == Models.EncounterType.Tug && iAm != Internal.Geo.AircraftRelation.OnTow))
            {
                target.End = context.CurrentPosition.TimeStamp;
                context.StateMachine.Fire(FlightContext.Trigger.LaunchCompleted);
                context.InvokeOnLaunchCompletedEvent();
                return;
            }
        }
Esempio n. 7
0
        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);
            }
        }
Esempio n. 8
0
        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 AircraftRelation TrackTow(this FlightContext context1, FlightContext context2)
        {
            if (context1.Flight.PositionUpdates.Count < 5 || context2.Flight.PositionUpdates.Count < 5)
            {
                return(AircraftRelation.None);
            }

            var interpolation = Interpolation.Interpolate(
                context1.Flight.PositionUpdates,
                context2.Flight.PositionUpdates,
                q => q.TimeStamp.Ticks,
                (object1, object2, time) =>
            {
                var dX = object2.Location.X - object1.Location.X;
                var dY = object2.Location.Y - object1.Location.Y;
                var dT = (object2.TimeStamp - object1.TimeStamp).Ticks;

                if (dT == 0)
                {
                    return(null);
                }

                double factor = (time - object1.TimeStamp.Ticks) / (double)dT;

                return(new PositionUpdate(
                           "",
                           new DateTime((long)(object1.TimeStamp.Ticks + dT * factor)),
                           object1.Location.Y + factor * dY,
                           object1.Location.X + factor * dX));
            });

            /*
             * We only care about the following two characteristics;
             * 1. In relation to the glider, the towplane is always in front -90 to +90 bearing
             * 2. The aircraft should be no more than 200 meters apart for any interpolated point
             *
             * As soon as any of these is false, the tow has ended.
             */

            bool?isTowed = null;

            foreach (var dataPoint in interpolation)
            {
                if (dataPoint.T1.Location.DistanceTo(dataPoint.T2.Location) > 200)
                {
                    break;
                }

                // Determine the position irt to the other aircraft
                var bearing = dataPoint.T1.Location.DegreeBearing(dataPoint.T2.Location);

                if (90 < bearing && bearing < 270)
                {
                    // We're being towed
                    if (isTowed == null)
                    {
                        isTowed = true;
                    }
                    else if (isTowed == false)
                    {
                        return(AircraftRelation.None);
                    }
                }
                else
                {
                    // We're towing
                    if (isTowed == null)
                    {
                        isTowed = false;
                    }
                    else if (isTowed == true)
                    {
                        return(AircraftRelation.None);
                    }
                }
            }

            if (isTowed == null)
            {
                return(AircraftRelation.None);
            }

            return(isTowed.Value
                ? AircraftRelation.OnTow
                : AircraftRelation.Towplane);
        }
Esempio n. 10
0
 /// <summary>
 /// The attach method can be used to add an already existing context instance to this factory.
 /// This method will overwrite any FlightContext instance with the same aircraft identifier already
 /// tracked by this FlightContextFactory.
 /// </summary>
 /// <param name="context"></param>
 public void Attach(FlightContext context) => Attach(context?.Flight);
Esempio n. 11
0
        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
            }
        }