/// <summary>
        /// Creates an object that represents an aircraft in <see cref="ProximityGadgetAircraftJson"/>.
        /// </summary>
        /// <param name="aircraft"></param>
        /// <param name="distance"></param>
        /// <param name="latitude"></param>
        /// <param name="longitude"></param>
        /// <returns></returns>
        private ProximityGadgetClosestAircraftJson CreateProximityGadgetClosestAircraftJson(IAircraft aircraft, double?distance, double?latitude, double?longitude)
        {
            var result = new ProximityGadgetClosestAircraftJson()
            {
                Altitude         = FormatNullable(aircraft.Altitude),
                BearingFromHere  = FormatNullable(GreatCircleMaths.Bearing(latitude, longitude, aircraft.Latitude, aircraft.Longitude, null, false, false), null, 1),
                Callsign         = aircraft.Callsign,
                Destination      = aircraft.Destination,
                DistanceFromHere = FormatNullable(distance, null, 2),
                Emergency        = aircraft.Emergency.GetValueOrDefault(),
                GroundSpeed      = FormatNullable(aircraft.GroundSpeed),
                HasPicture       = !String.IsNullOrEmpty(aircraft.PictureFileName),
                Icao24           = aircraft.Icao24 ?? "",
                Icao24Invalid    = aircraft.Icao24Invalid,
                Latitude         = FormatNullable(aircraft.Latitude),
                Longitude        = FormatNullable(aircraft.Longitude),
                Manufacturer     = aircraft.Manufacturer,
                Model            = aircraft.Model,
                Operator         = aircraft.Operator,
                OperatorIcao     = aircraft.OperatorIcao,
                Origin           = aircraft.Origin,
                Registration     = aircraft.Registration,
                Squawk           = aircraft.Squawk.GetValueOrDefault() == 0 ? null : String.Format("{0:0000}", aircraft.Squawk),
                Track            = FormatNullable(aircraft.Track, null, 1),
                Type             = aircraft.Type,
                VerticalRate     = FormatNullable(aircraft.VerticalRate),
            };

            result.Stopovers.AddRange(aircraft.Stopovers);

            return(result);
        }
示例#2
0
        public void CompactPositionReporting_GlobalDecode_Calculates_Correct_Surface_Position()
        {
            foreach (var startLatitude in new double[] { 17.12345, -17.12345 })
            {
                foreach (var startLongitude in new double[] { 145.12345, 55.12345, -35.12345, -125.12345 })
                {
                    var startLocation = new GlobalCoordinate(startLatitude, startLongitude);

                    double?receiverLatitude, receiverLongitude;
                    GreatCircleMaths.Destination(startLatitude, startLongitude, 70, 80, out receiverLatitude, out receiverLongitude);
                    var receiverLocation = new GlobalCoordinate(receiverLatitude.GetValueOrDefault(), receiverLongitude.GetValueOrDefault());

                    double?endLatitude, endLongitude;
                    GreatCircleMaths.Destination(startLatitude, startLongitude, 90, 1.4, out endLatitude, out endLongitude);
                    var endLocation = new GlobalCoordinate(endLatitude.GetValueOrDefault(), endLongitude.GetValueOrDefault());

                    var earlyCpr = _Cpr.Encode(startLocation, false, 19);
                    var laterCpr = _Cpr.Encode(endLocation, true, 19);

                    var decoded = _Cpr.GlobalDecode(earlyCpr, laterCpr, endLocation);

                    var errorMessage = String.Format("Start: {0}, end (expected): {1}, receiver: {2}, decoded: {3}, earlyCpr: {4}, laterCpr: {5}", startLocation, endLocation, receiverLocation, decoded, earlyCpr, laterCpr);
                    Assert.AreEqual(endLocation.Latitude, decoded.Latitude, 0.00001, errorMessage);
                    Assert.AreEqual(endLocation.Longitude, decoded.Longitude, 0.00001, errorMessage);
                }
            }
        }
示例#3
0
        public void GreatCircleMaths_Destination_Calculates_Correct_Destination()
        {
            var worksheet = new ExcelWorksheetData(TestContext);

            var startLatitude  = worksheet.NDouble("StartLatitude");
            var startLongitude = worksheet.NDouble("StartLongitude");
            var bearing        = worksheet.NDouble("Bearing");
            var distance       = worksheet.NDouble("Distance");

            double?endLatitude, endLongitude;

            GreatCircleMaths.Destination(startLatitude, startLongitude, bearing, distance, out endLatitude, out endLongitude);

            var expectedLatitude  = worksheet.NDouble("EndLatitude");
            var expectedLongitude = worksheet.NDouble("EndLongitude");

            if (expectedLatitude == null || expectedLongitude == null)
            {
                Assert.AreEqual(expectedLatitude, endLatitude);
                Assert.AreEqual(expectedLongitude, endLongitude);
            }
            else
            {
                Assert.AreEqual(expectedLatitude.Value, endLatitude.Value, 0.0001);
                Assert.AreEqual(expectedLongitude.Value, endLongitude.Value, 0.0001);
            }
        }
示例#4
0
        /// <summary>
        /// Returns a filtered list of aircraft and at the same time calculates the distances from the browser location to each aircraft.
        /// </summary>
        /// <param name="aircraftListSnapshot"></param>
        /// <param name="args"></param>
        /// <param name="distances"></param>
        /// <returns></returns>
        /// <remarks>Distance calculations can be expensive, hence the reason why we try to minimise the number of times that they are performed.</remarks>
        private List <IAircraft> FilterAircraft(List <IAircraft> aircraftListSnapshot, AircraftListJsonBuilderArgs args, ref Dictionary <int, double?> distances)
        {
            List <IAircraft> result = new List <IAircraft>();

            foreach (var aircraft in aircraftListSnapshot)
            {
                if (!PassesFilter(aircraft, args.Filter))
                {
                    continue;
                }

                var distance = args.IsFlightSimulatorList ? null : GreatCircleMaths.Distance(args.BrowserLatitude, args.BrowserLongitude, aircraft.Latitude, aircraft.Longitude);
                if (args.Filter != null)
                {
                    if (args.Filter.DistanceLower != null && (distance == null || distance < args.Filter.DistanceLower))
                    {
                        continue;
                    }
                    if (args.Filter.DistanceUpper != null && (distance == null || distance > args.Filter.DistanceUpper))
                    {
                        continue;
                    }
                }

                result.Add(aircraft);
                distances.Add(aircraft.UniqueId, distance);
            }

            return(result);
        }
示例#5
0
        /// <summary>
        /// See base class.
        /// </summary>
        /// <param name="server"></param>
        /// <param name="args"></param>
        /// <returns></returns>
        protected override bool DoHandleRequest(Interface.WebServer.IWebServer server, Interface.WebServer.RequestReceivedEventArgs args)
        {
            bool result = false;

            if ((!args.IsInternetRequest || _InternetClientCanRequestClosestAircraft) && args.PathAndFile.Equals("/ClosestAircraft.json", StringComparison.OrdinalIgnoreCase))
            {
                result = true;

                var latitude  = QueryNDouble(args, "lat");
                var longitude = QueryNDouble(args, "lng");

                var json = new ProximityGadgetAircraftJson();

                if (latitude == null || longitude == null)
                {
                    json.WarningMessage = "Position not supplied";
                }
                else
                {
                    long timeStamp, dataVersion;
                    var  aircraftList = BaseStationAircraftList.TakeSnapshot(out timeStamp, out dataVersion);

                    IAircraft closestAircraft = null;
                    double?   closestDistance = null;

                    foreach (var aircraft in aircraftList)
                    {
                        double?distance = null;
                        if (aircraft.Latitude != null && aircraft.Longitude != null)
                        {
                            distance = GreatCircleMaths.Distance(latitude, longitude, aircraft.Latitude, aircraft.Longitude);
                            if (distance != null && closestAircraft == null || distance < closestDistance)
                            {
                                closestAircraft = aircraft;
                                closestDistance = distance;
                            }
                        }

                        if (aircraft.Emergency == true)
                        {
                            json.EmergencyAircraft.Add(CreateProximityGadgetClosestAircraftJson(aircraft, distance, latitude, longitude));
                        }
                    }

                    if (closestAircraft != null)
                    {
                        var closestJsonAircraft = CreateProximityGadgetClosestAircraftJson(closestAircraft, closestDistance, latitude, longitude);
                        json.ClosestAircraft = closestJsonAircraft;
                    }
                }

                Responder.SendJson(args.Response, json, null);
                args.Classification = ContentClassification.Json;
            }

            return(result);
        }
示例#6
0
        public void CompactPositionReporting_Encode_GlobalDecode_Full_Globe_Round_Trip()
        {
            foreach (var numberOfBits in new byte[] { 17, 19 })
            {
                var expectedAccuracy = 0.0;
                switch (numberOfBits)
                {
                case 17:    expectedAccuracy = 0.002; break;

                case 19:    expectedAccuracy = 0.0004; break;
                }

                var latitudeResolution  = 0.25;
                var longitudeResolution = 0.25;
                var endLocation         = new GlobalCoordinate();
                for (var latitude = -89.75; latitude < 90.0; latitude += latitudeResolution)    // global decoding at the poles can produce odd results
                {
                    for (var longitude = -180.0; longitude <= 180.0; longitude += longitudeResolution)
                    {
                        endLocation.Latitude  = latitude;
                        endLocation.Longitude = longitude;

                        var startIsNorth = true;
                        if (latitude == 87.0 || latitude == -80.25 || latitude == 85.75 || latitude == 90.0)
                        {
                            startIsNorth = false;                                                                                 // a start position north of the end position would be in a different NL for these
                        }
                        var bearing = startIsNorth ? 315 : 225;

                        double?startLatitude, startLongitude;
                        GreatCircleMaths.Destination(latitude, longitude, bearing, 1, out startLatitude, out startLongitude);
                        var startLocation = new GlobalCoordinate(startLatitude.Value, startLongitude.Value);

                        var earlyCpr = _Cpr.Encode(startLocation, true, numberOfBits);
                        var laterCpr = _Cpr.Encode(endLocation, false, numberOfBits);

                        var decoded = _Cpr.GlobalDecode(earlyCpr, laterCpr, startLocation);
                        Assert.IsNotNull(decoded, "Returned null for start {0}, end {1} (early CPR {2} later CPR {3})", startLocation, endLocation, earlyCpr, laterCpr);

                        // Longitudes 180 and -180 are the same - if the decoded longitude has one sign and the expected longitude has the other then that's fine
                        if (longitude == -180.0 && decoded.Longitude == 180.0)
                        {
                            decoded.Longitude = -180.0;
                        }
                        else if (longitude == 180.0 && decoded.Longitude == -180.0)
                        {
                            decoded.Longitude = 180.0;
                        }

                        Assert.AreEqual(latitude, decoded.Latitude, expectedAccuracy, "Latitude incorrect for start {0}, end {1} (early CPR {2} later CPR {3})", startLocation, endLocation, earlyCpr, laterCpr);
                        Assert.AreEqual(longitude, decoded.Longitude, expectedAccuracy, "Longitude incorrect for start {0}, end {1} (early CPR {2} later CPR {3})", startLocation, endLocation, earlyCpr, laterCpr);
                    }
                }
            }
        }
示例#7
0
        /// <summary>
        /// Returns the distance from the browser to the aircraft. If the aircraft is in the <see cref="PrecalculatedDistances"/> map then
        /// the pre-calculated distance is returned.
        /// </summary>
        /// <param name="aircraft"></param>
        /// <returns></returns>
        private double?CalculateDistance(IAircraft aircraft)
        {
            double?result;

            if (!PrecalculatedDistances.TryGetValue(aircraft.UniqueId, out result))
            {
                result = GreatCircleMaths.Distance(BrowserLocation.Latitude, BrowserLocation.Longitude, aircraft.Latitude, aircraft.Longitude);
            }

            return(result);
        }
示例#8
0
        public void CompactPositionReporting_GlobalDecode_Calculates_Correct_Position_From_Two_Coordinates()
        {
            var firstLocation  = new GlobalCoordinate(54.12345, -0.61234);
            var secondLocation = new GlobalCoordinate(54.17, -0.5801);
            var distance       = GreatCircleMaths.Distance(firstLocation.Latitude, firstLocation.Longitude, secondLocation.Latitude, secondLocation.Longitude);

            var earlyCpr = _Cpr.Encode(firstLocation, false, 17);
            var laterCpr = _Cpr.Encode(secondLocation, true, 17);

            var decoded = _Cpr.GlobalDecode(earlyCpr, laterCpr, null);

            Assert.AreEqual(secondLocation.Latitude, decoded.Latitude, 0.0001);
            Assert.AreEqual(secondLocation.Longitude, decoded.Longitude, 0.0001);
        }
示例#9
0
        public void PolarPlotter_AddCheckedCoordinate_Ignores_Positions_Further_Than_Radar_Range()
        {
            _Configuration.RawDecodingSettings.ReceiverRange = 650;
            StandardInitialise();
            double?latitude, longitude;

            GreatCircleMaths.Destination(_Plotter.Latitude, _Plotter.Longitude, 90, 650.001, out latitude, out longitude);

            _Plotter.AddCheckedCoordinate(1, 12, latitude.Value, longitude.Value);

            var slices = _Plotter.TakeSnapshot();

            Assert.AreEqual(0, slices.Count(r => r.PolarPlots.Count(i => i.Value.Distance != 0) > 0));
        }
示例#10
0
        /// <summary>
        /// Calcualtes whether we are certain than an aircraft could potentially move between two points in a given span of time.
        /// </summary>
        /// <param name="seenGoodPosition"></param>
        /// <param name="previousPosition"></param>
        /// <param name="thisPosition"></param>
        /// <returns></returns>
        private Certainty CalculatePositionCertainty(bool seenGoodPosition, TimedValue <GlobalCoordinate> previousPosition, TimedValue <GlobalCoordinate> thisPosition)
        {
            var result = Certainty.Uncertain;

            if (thisPosition.Value.Latitude != 0.0 || thisPosition.Value.Longitude != 0.0)
            {
                var distance = GreatCircleMaths.Distance(previousPosition.Value.Latitude, previousPosition.Value.Longitude, thisPosition.Value.Latitude, thisPosition.Value.Longitude);
                var time     = (thisPosition.Time - previousPosition.Time).TotalSeconds;
                var speed    = distance / time;

                result = speed <= MaxSpeedKilometersPerSecond ? Certainty.ProbablyRight
                                                              : seenGoodPosition ? Certainty.CertainlyWrong : Certainty.Uncertain;
            }

            return(result);
        }
示例#11
0
        /// <summary>
        /// Returns a model given an aircraft list and a point to measure from.
        /// </summary>
        /// <param name="aircraftList"></param>
        /// <param name="originLatitude"></param>
        /// <param name="originLongitude"></param>
        /// <returns></returns>
        public static ProximityGadgetAircraftJson ToModel(IEnumerable <IAircraft> aircraftList, double?originLatitude, double?originLongitude)
        {
            ProximityGadgetAircraftJson result = null;

            if (aircraftList != null)
            {
                result = new ProximityGadgetAircraftJson();

                if (originLatitude == null || originLongitude == null)
                {
                    result.WarningMessage = "Position not supplied";
                }
                else
                {
                    IAircraft closestAircraft = null;
                    double?   closestDistance = null;

                    foreach (var aircraft in aircraftList)
                    {
                        double?distance = null;
                        if (aircraft.Latitude != null && aircraft.Longitude != null)
                        {
                            distance = GreatCircleMaths.Distance(originLatitude, originLongitude, aircraft.Latitude, aircraft.Longitude);
                            if (distance != null && closestAircraft == null || distance < closestDistance)
                            {
                                closestAircraft = aircraft;
                                closestDistance = distance;
                            }
                        }

                        if (aircraft.Emergency == true)
                        {
                            result.EmergencyAircraft.Add(ProximityGadgetClosestAircraftJson.ToModel(aircraft, originLatitude, originLongitude));
                        }
                    }

                    if (closestAircraft != null)
                    {
                        result.ClosestAircraft = ProximityGadgetClosestAircraftJson.ToModel(closestAircraft, originLatitude, originLongitude);
                    }
                }
            }

            return(result);
        }
示例#12
0
        private void TestBearings(int roundToDegrees, Dictionary <double, int> bearings)
        {
            foreach (var kvp in bearings)
            {
                var actualBearing   = kvp.Key;
                var expectedBearing = kvp.Value;

                _Plotter.Initialise(51, -0.6, 0, 19, 10, roundToDegrees);

                double?latitude, longitude;
                GreatCircleMaths.Destination(_Plotter.Latitude, _Plotter.Longitude, actualBearing, 500, out latitude, out longitude);
                _Plotter.AddCoordinate(1, 5, latitude.Value, longitude.Value);

                var slices = _Plotter.TakeSnapshot();
                var plot   = slices.Single(r => r.AltitudeLower == 0 && r.AltitudeHigher == 9).PolarPlots.Values.Single(i => i.Distance != 0);

                Assert.AreEqual(expectedBearing, plot.Angle, "actualBearing {0} rounded to {1}°", actualBearing, roundToDegrees);
            }
        }
示例#13
0
        public void GreatCircleMaths_Distance_Calculates_Correct_Distances()
        {
            var worksheet = new ExcelWorksheetData(TestContext);

            var startLatitude  = worksheet.NDouble("StartLatitude");
            var startLongitude = worksheet.NDouble("StartLongitude");
            var endLatitude    = worksheet.NDouble("EndLatitude");
            var endLongitude   = worksheet.NDouble("EndLongitude");
            var expected       = worksheet.NDouble("Distance");

            var actual = GreatCircleMaths.Distance(startLatitude, startLongitude, endLatitude, endLongitude);

            if (expected == null)
            {
                Assert.IsNull(actual);
            }
            else
            {
                Assert.AreEqual((double)expected, (double)actual, 0.0001);
            }
        }
示例#14
0
        public void AircraftSanityChecker_FirstGoodPosition_Returns_Correct_Values()
        {
            var worksheet = new ExcelWorksheetData(TestContext);
            //if(!worksheet.NBool("JustThis").GetValueOrDefault()) continue;

            var comments = worksheet.String("Comments");

            for (var i = 1; i <= 5; ++i)
            {
                var distance = worksheet.NDouble(String.Format("Distance{0}", i));
                if (distance != null)
                {
                    var seconds        = worksheet.Double(String.Format("Seconds{0}", i));
                    var time           = new DateTime(2014, 8, 3).AddSeconds(seconds);
                    var expectedResult = worksheet.NDouble(String.Format("1stGood{0}", i));

                    double?latitude, longitude;
                    GreatCircleMaths.Destination(51.0, -0.6, 90.0, distance, out latitude, out longitude);

                    _Checker.CheckPosition(1, time, latitude.Value, longitude.Value);
                    var globalCoordinates = _Checker.FirstGoodPosition(1);

                    double?actualResult = null;
                    if (globalCoordinates != null)
                    {
                        actualResult = GreatCircleMaths.Distance(51.0, -0.6, globalCoordinates.Latitude, globalCoordinates.Longitude);
                    }

                    var message = String.Format("Column {0} {1}", i, comments);
                    if (expectedResult == null)
                    {
                        Assert.IsNull(actualResult, message);
                    }
                    else
                    {
                        Assert.AreEqual(expectedResult.Value, actualResult ?? double.MinValue, 0.001, message);
                    }
                }
            }
        }
示例#15
0
        public void GreatCircleMaths_Bearing_Calculates_Correct_Bearing()
        {
            var worksheet = new ExcelWorksheetData(TestContext);

            var startLatitude  = worksheet.NDouble("StartLatitude");
            var startLongitude = worksheet.NDouble("StartLongitude");
            var endLatitude    = worksheet.NDouble("EndLatitude");
            var endLongitude   = worksheet.NDouble("EndLongitude");
            var currentTrack   = worksheet.NDouble("CurrentTrack");
            var expected       = worksheet.NDouble("Bearing");

            var actual = GreatCircleMaths.Bearing(startLatitude, startLongitude, endLatitude, endLongitude, currentTrack, worksheet.Bool("ReverseBearing"), worksheet.Bool("IgnoreCurrentTrack"));

            if (expected == null)
            {
                Assert.IsNull(actual);
            }
            else
            {
                Assert.AreEqual((double)expected, (double)actual, 0.0001);
            }
        }
示例#16
0
        /// <summary>
        /// See interface docs.
        /// </summary>
        /// <param name="aircraftId"></param>
        /// <param name="altitude"></param>
        /// <param name="latitude"></param>
        /// <param name="longitude"></param>
        public void AddCheckedCoordinate(int aircraftId, int altitude, double latitude, double longitude)
        {
            if (RoundToDegrees > 0)
            {
                var distance    = GreatCircleMaths.Distance(Latitude, Longitude, latitude, longitude);
                var fullBearing = GreatCircleMaths.Bearing(Latitude, Longitude, latitude, longitude, null, false, true);

                if (distance != null && fullBearing != null && distance <= _ReceiverRange)
                {
                    var roundedBearing = RoundBearing(fullBearing.Value);

                    lock (_SyncLock) {
                        foreach (var slice in _Slices)
                        {
                            if (slice.AltitudeLower <= altitude && slice.AltitudeHigher >= altitude)
                            {
                                PolarPlot plot;
                                if (!slice.PolarPlots.TryGetValue(roundedBearing, out plot))
                                {
                                    plot = new PolarPlot();
                                    slice.PolarPlots.Add(roundedBearing, plot);
                                }
                                if (distance >= plot.Distance)
                                {
                                    plot.Altitude  = altitude;
                                    plot.Angle     = roundedBearing;
                                    plot.Distance  = distance.Value;
                                    plot.Latitude  = latitude;
                                    plot.Longitude = longitude;
                                }
                            }
                        }
                    }
                }
            }
        }
示例#17
0
        public void AircraftSanityChecker_CheckPosition_Returns_Correct_Values()
        {
            var worksheet = new ExcelWorksheetData(TestContext);
            //if(!worksheet.NBool("JustThis").GetValueOrDefault()) continue;

            var comments = worksheet.String("Comments");

            for (var i = 1; i <= 5; ++i)
            {
                var distance = worksheet.NDouble(String.Format("Distance{0}", i));
                if (distance != null)
                {
                    var seconds        = worksheet.Double(String.Format("Seconds{0}", i));
                    var time           = new DateTime(2014, 8, 3).AddSeconds(seconds);
                    var expectedResult = worksheet.ParseEnum <Certainty>(String.Format("Result{0}", i));

                    double?latitude, longitude;
                    GreatCircleMaths.Destination(51.0, -0.6, 90.0, distance, out latitude, out longitude);

                    var actualResult = _Checker.CheckPosition(1, time, latitude.Value, longitude.Value);
                    Assert.AreEqual(expectedResult, actualResult, String.Format("Column {0} {1}", i, comments));
                }
            }
        }
示例#18
0
        public void CompactPositionReporting_LocalDecode_Produces_Correct_Results_For_CPR101_Tables()
        {
            // The values for this test are all taken directly from the transition latitude test tables in 1090-WP30-12 Proposed New Appendix CPR101
            var worksheet = new ExcelWorksheetData(TestContext);

            var numberOfBits      = worksheet.Byte("Bits");
            var oddFormat         = worksheet.Bool("OddFormat");
            var encodedLatitude   = Convert.ToInt32(worksheet.String("ExpectedLatitude"), 16);
            var encodedLongitude  = Convert.ToInt32(worksheet.String("ExpectedLongitude"), 16);
            var expectedLatitude  = worksheet.Double("Latitude");
            var expectedLongitude = worksheet.Double("Longitude");
            var cprCoordinate     = new CompactPositionReportingCoordinate(encodedLatitude, encodedLongitude, oddFormat, numberOfBits);

            // The reference latitude and longitude is set to roughly 50km of the expected latitude and longitude
            double?referenceLatitude, referenceLongitude;

            GreatCircleMaths.Destination(expectedLatitude, expectedLongitude, 45, 50, out referenceLatitude, out referenceLongitude);
            var referenceCoordinate = new GlobalCoordinate(referenceLatitude.Value, referenceLongitude.Value);

            var dataRow = worksheet.Int("DataRow"); // helps set conditional breakpoints, VSTS doesn't always process rows in ascending order as they appear in the worksheet

            var decodedCoordinate = _Cpr.LocalDecode(cprCoordinate, referenceCoordinate);

            // We need to accept 180 and -180 as being the same longitude, taking into account rounding errors
            if (expectedLongitude == -180.0 && decodedCoordinate.Longitude > 179.9999999999)
            {
                expectedLongitude = 180.0;
            }
            else if (expectedLongitude == 180.0 && decodedCoordinate.Longitude < -179.9999999999)
            {
                expectedLongitude = -180.0;
            }

            Assert.AreEqual(expectedLatitude, decodedCoordinate.Latitude, 0.0008);           // The CPR tables cover all latitudes, sometimes the rounding introduced by selecting the midpoint of a zone can be quite large
            Assert.AreEqual(expectedLongitude, decodedCoordinate.Longitude, 0.000000000001); // This can have a lower tolerance as the CPR101 tables aren't testing longitude zone boundaries so much
        }
示例#19
0
        /// <summary>
        /// Returns true if the aircraft passes the filter criteria passed across.
        /// </summary>
        /// <param name="aircraft"></param>
        /// <param name="args"></param>
        /// <param name="distances"></param>
        /// <returns></returns>
        private bool PassesFilter(IAircraft aircraft, AircraftListJsonBuilderArgs args, Dictionary <int, double?> distances)
        {
            var  filter = args.Filter;
            bool result = filter == null;

            var distance = args.IsFlightSimulatorList ? null : GreatCircleMaths.Distance(args.BrowserLatitude, args.BrowserLongitude, aircraft.Latitude, aircraft.Longitude);

            if (!result)
            {
                result = true;
                if (result && filter.Altitude != null)
                {
                    result = filter.Altitude.Passes(aircraft.Altitude);
                }
                if (result && filter.Callsign != null)
                {
                    result = filter.Callsign.Passes(aircraft.Callsign);
                }
                if (result && filter.EngineType != null)
                {
                    result = filter.EngineType.Passes(aircraft.EngineType);
                }
                if (result && filter.Icao24 != null)
                {
                    result = filter.Icao24.Passes(aircraft.Icao24);
                }
                if (result && filter.Icao24Country != null)
                {
                    result = filter.Icao24Country.Passes(aircraft.Icao24Country);
                }
                if (result && filter.IsInteresting != null)
                {
                    result = filter.IsInteresting.Passes(aircraft.IsInteresting);
                }
                if (result && filter.IsMilitary != null)
                {
                    result = filter.IsMilitary.Passes(aircraft.IsMilitary);
                }
                if (result && filter.MustTransmitPosition != null)
                {
                    result = filter.MustTransmitPosition.Passes(aircraft.Latitude != null && aircraft.Longitude != null);
                }
                if (result && filter.Operator != null)
                {
                    result = filter.Operator.Passes(aircraft.Operator);
                }
                if (result && filter.OperatorIcao != null)
                {
                    result = filter.OperatorIcao.Passes(aircraft.OperatorIcao);
                }
                if (result && filter.PositionWithin != null)
                {
                    result = args.SelectedAircraftId == aircraft.UniqueId || IsWithinBounds(aircraft.Latitude, aircraft.Longitude, filter.PositionWithin);
                }
                if (result && filter.Registration != null)
                {
                    result = filter.Registration.Passes(aircraft.Registration);
                }
                if (result && filter.Species != null)
                {
                    result = filter.Species.Passes(aircraft.Species);
                }
                if (result && filter.Squawk != null)
                {
                    result = filter.Squawk.Passes(aircraft.Squawk);
                }
                if (result && filter.Type != null)
                {
                    result = filter.Type.Passes(aircraft.Type);
                }
                if (result && filter.UserTag != null)
                {
                    result = filter.UserTag.Passes(aircraft.UserTag);
                }
                if (result && filter.WakeTurbulenceCategory != null)
                {
                    result = filter.WakeTurbulenceCategory.Passes(aircraft.WakeTurbulenceCategory);
                }

                if (result && filter.Airport != null)
                {
                    result = PassesAirportFilter(filter.Airport, aircraft);
                }

                if (result && filter.Distance != null)
                {
                    if (distance == null && filter.Distance.IsValid)
                    {
                        result = false;
                    }
                    else
                    {
                        result = filter.Distance.Passes(distance);
                    }
                }
            }

            if (result)
            {
                distances.Add(aircraft.UniqueId, distance);
            }

            return(result);
        }
示例#20
0
        /// <summary>
        /// Copies the aircraft from the snapshot to the JSON object.
        /// </summary>
        /// <param name="aircraftListJson"></param>
        /// <param name="aircraftListSnapshot"></param>
        /// <param name="args"></param>
        /// <param name="distances"></param>
        private void CopyAircraft(AircraftListJson aircraftListJson, List <IAircraft> aircraftListSnapshot, AircraftListJsonBuilderArgs args, Dictionary <int, double?> distances)
        {
            var now                      = Provider.UtcNow;
            var configuration            = _SharedConfiguration.Get();
            var positionTimeoutThreshold = now.AddSeconds(-(configuration.BaseStationSettings.DisplayTimeoutSeconds + BoostStalePositionSeconds));

            HashSet <int> previousAircraftSet = null;
            List <int>    previousAircraft    = args.PreviousAircraft;

            if (previousAircraft.Count > 15)
            {
                previousAircraftSet = new HashSet <int>(previousAircraft);
                previousAircraft    = null;
            }

            double?distance = null;

            for (var i = 0; i < aircraftListSnapshot.Count; ++i)
            {
                var aircraftSnapshot = aircraftListSnapshot[i];
                if (distances != null)
                {
                    distances.TryGetValue(aircraftSnapshot.UniqueId, out distance);
                }

                var aircraftJson = new AircraftJson()
                {
                    UniqueId     = aircraftSnapshot.UniqueId,
                    IsSatcomFeed = aircraftSnapshot.LastSatcomUpdate != DateTime.MinValue,
                };
                if (!args.OnlyIncludeMessageFields)
                {
                    aircraftJson.BearingFromHere  = GreatCircleMaths.Bearing(args.BrowserLatitude, args.BrowserLongitude, aircraftSnapshot.Latitude, aircraftSnapshot.Longitude, null, false, true);
                    aircraftJson.DistanceFromHere = distance == null ? (double?)null : Math.Round(distance.Value, 2);
                    if (aircraftJson.BearingFromHere != null)
                    {
                        aircraftJson.BearingFromHere = Math.Round(aircraftJson.BearingFromHere.Value, 1);
                    }
                }

                var firstTimeSeen = previousAircraft != null ? !previousAircraft.Contains(aircraftSnapshot.UniqueId)
                                                             : !previousAircraftSet.Contains(aircraftSnapshot.UniqueId);

                if (firstTimeSeen || aircraftSnapshot.AirPressureInHgChanged > args.PreviousDataVersion)
                {
                    aircraftJson.AirPressureInHg = aircraftSnapshot.AirPressureInHg;
                }
                if (firstTimeSeen || aircraftSnapshot.AltitudeChanged > args.PreviousDataVersion)
                {
                    aircraftJson.Altitude = aircraftSnapshot.Altitude;
                }
                if (firstTimeSeen || aircraftSnapshot.AltitudeTypeChanged > args.PreviousDataVersion)
                {
                    aircraftJson.AltitudeType = (int)aircraftSnapshot.AltitudeType;
                }
                if (firstTimeSeen || aircraftSnapshot.CallsignChanged > args.PreviousDataVersion)
                {
                    aircraftJson.Callsign = aircraftSnapshot.Callsign;
                }
                if (firstTimeSeen || aircraftSnapshot.CallsignIsSuspectChanged > args.PreviousDataVersion)
                {
                    aircraftJson.CallsignIsSuspect = aircraftSnapshot.CallsignIsSuspect;
                }
                if (firstTimeSeen || aircraftSnapshot.GroundSpeedChanged > args.PreviousDataVersion)
                {
                    aircraftJson.GroundSpeed = Round.GroundSpeed(aircraftSnapshot.GroundSpeed);
                }
                if (firstTimeSeen || aircraftSnapshot.EmergencyChanged > args.PreviousDataVersion)
                {
                    aircraftJson.Emergency = aircraftSnapshot.Emergency;
                }
                if (firstTimeSeen || aircraftSnapshot.GeometricAltitudeChanged > args.PreviousDataVersion)
                {
                    aircraftJson.GeometricAltitude = aircraftSnapshot.GeometricAltitude;
                }
                if (firstTimeSeen || args.AlwaysShowIcao || aircraftSnapshot.Icao24Changed > args.PreviousDataVersion)
                {
                    aircraftJson.Icao24 = aircraftSnapshot.Icao24;
                }
                if (firstTimeSeen || aircraftSnapshot.IsTisbChanged > args.PreviousDataVersion)
                {
                    aircraftJson.IsTisb = aircraftSnapshot.IsTisb;
                }
                if (firstTimeSeen || aircraftSnapshot.LatitudeChanged > args.PreviousDataVersion)
                {
                    aircraftJson.Latitude = Round.Coordinate(aircraftSnapshot.Latitude);
                }
                if (firstTimeSeen || aircraftSnapshot.LongitudeChanged > args.PreviousDataVersion)
                {
                    aircraftJson.Longitude = Round.Coordinate(aircraftSnapshot.Longitude);
                }
                if (firstTimeSeen || aircraftSnapshot.OnGroundChanged > args.PreviousDataVersion)
                {
                    aircraftJson.OnGround = aircraftSnapshot.OnGround;
                }
                if (firstTimeSeen || aircraftSnapshot.PositionIsMlatChanged > args.PreviousDataVersion)
                {
                    aircraftJson.PositionIsMlat = aircraftSnapshot.PositionIsMlat;
                }
                if (firstTimeSeen || aircraftSnapshot.SignalLevelChanged > args.PreviousDataVersion)
                {
                    aircraftJson.HasSignalLevel = aircraftSnapshot.SignalLevel != null; aircraftJson.SignalLevel = aircraftSnapshot.SignalLevel;
                }
                if (firstTimeSeen || aircraftSnapshot.SpeedTypeChanged > args.PreviousDataVersion)
                {
                    aircraftJson.SpeedType = (int)aircraftSnapshot.SpeedType;
                }
                if (firstTimeSeen || aircraftSnapshot.SquawkChanged > args.PreviousDataVersion)
                {
                    aircraftJson.Squawk = String.Format("{0:0000}", aircraftSnapshot.Squawk);
                }
                if (firstTimeSeen || aircraftSnapshot.TargetAltitudeChanged > args.PreviousDataVersion)
                {
                    aircraftJson.TargetAltitude = aircraftSnapshot.TargetAltitude;
                }
                if (firstTimeSeen || aircraftSnapshot.TargetTrackChanged > args.PreviousDataVersion)
                {
                    aircraftJson.TargetTrack = aircraftSnapshot.TargetTrack;
                }
                if (firstTimeSeen || aircraftSnapshot.TrackChanged > args.PreviousDataVersion)
                {
                    aircraftJson.Track = Round.Track(aircraftSnapshot.Track);
                }
                if (firstTimeSeen || aircraftSnapshot.TrackIsHeadingChanged > args.PreviousDataVersion)
                {
                    aircraftJson.TrackIsHeading = aircraftSnapshot.TrackIsHeading;
                }
                if (firstTimeSeen || aircraftSnapshot.TransponderTypeChanged > args.PreviousDataVersion)
                {
                    aircraftJson.TransponderType = (int)aircraftSnapshot.TransponderType;
                }
                if (firstTimeSeen || aircraftSnapshot.VerticalRateChanged > args.PreviousDataVersion)
                {
                    aircraftJson.VerticalRate = aircraftSnapshot.VerticalRate;
                }
                if (firstTimeSeen || aircraftSnapshot.VerticalRateTypeChanged > args.PreviousDataVersion)
                {
                    aircraftJson.VerticalRateType = (int)aircraftSnapshot.VerticalRateType;
                }

                if (args.OnlyIncludeMessageFields)
                {
                    if (aircraftJson.Latitude != null || aircraftJson.Longitude != null || aircraftJson.PositionIsMlat != null)
                    {
                        if (aircraftJson.Latitude == null)
                        {
                            aircraftJson.Latitude = Round.Coordinate(aircraftSnapshot.Latitude);
                        }
                        if (aircraftJson.Longitude == null)
                        {
                            aircraftJson.Longitude = Round.Coordinate(aircraftSnapshot.Longitude);
                        }
                        if (aircraftJson.PositionIsMlat == null)
                        {
                            aircraftJson.PositionIsMlat = aircraftSnapshot.PositionIsMlat;
                        }
                    }
                    if (aircraftJson.Altitude != null || aircraftJson.GeometricAltitude != null || aircraftJson.AltitudeType != null)
                    {
                        if (aircraftJson.Altitude == null)
                        {
                            aircraftJson.Altitude = aircraftSnapshot.Altitude;
                        }
                        if (aircraftJson.AltitudeType == null)
                        {
                            aircraftJson.AltitudeType = (int)aircraftSnapshot.AltitudeType;
                        }
                        if (aircraftJson.GeometricAltitude == null)
                        {
                            aircraftJson.GeometricAltitude = aircraftSnapshot.GeometricAltitude;
                        }
                    }
                }
                else if (!args.OnlyIncludeMessageFields)
                {
                    if (firstTimeSeen || aircraftSnapshot.ConstructionNumberChanged > args.PreviousDataVersion)
                    {
                        aircraftJson.ConstructionNumber = aircraftSnapshot.ConstructionNumber;
                    }
                    if (firstTimeSeen || aircraftSnapshot.CountMessagesReceivedChanged > args.PreviousDataVersion)
                    {
                        aircraftJson.CountMessagesReceived = aircraftSnapshot.CountMessagesReceived;
                    }
                    if (firstTimeSeen || aircraftSnapshot.DestinationChanged > args.PreviousDataVersion)
                    {
                        aircraftJson.Destination = aircraftSnapshot.Destination;
                    }
                    if (firstTimeSeen || aircraftSnapshot.EnginePlacementChanged > args.PreviousDataVersion)
                    {
                        aircraftJson.EnginePlacement = (int)aircraftSnapshot.EnginePlacement;
                    }
                    if (firstTimeSeen || aircraftSnapshot.EngineTypeChanged > args.PreviousDataVersion)
                    {
                        aircraftJson.EngineType = (int)aircraftSnapshot.EngineType;
                    }
                    if (firstTimeSeen || aircraftSnapshot.FirstSeenChanged > args.PreviousDataVersion)
                    {
                        aircraftJson.FirstSeen = aircraftSnapshot.FirstSeen;
                    }
                    if (firstTimeSeen || aircraftSnapshot.FlightsCountChanged > args.PreviousDataVersion)
                    {
                        aircraftJson.FlightsCount = aircraftSnapshot.FlightsCount;
                    }
                    if (firstTimeSeen || aircraftSnapshot.Icao24CountryChanged > args.PreviousDataVersion)
                    {
                        aircraftJson.Icao24Country = aircraftSnapshot.Icao24Country;
                    }
                    if (firstTimeSeen || aircraftSnapshot.Icao24InvalidChanged > args.PreviousDataVersion)
                    {
                        aircraftJson.Icao24Invalid = aircraftSnapshot.Icao24Invalid;
                    }
                    if (firstTimeSeen || aircraftSnapshot.IsInterestingChanged > args.PreviousDataVersion)
                    {
                        aircraftJson.IsInteresting = aircraftSnapshot.IsInteresting;
                    }
                    if (firstTimeSeen || aircraftSnapshot.IsMilitaryChanged > args.PreviousDataVersion)
                    {
                        aircraftJson.IsMilitary = aircraftSnapshot.IsMilitary;
                    }
                    if (firstTimeSeen || aircraftSnapshot.ManufacturerChanged > args.PreviousDataVersion)
                    {
                        aircraftJson.Manufacturer = aircraftSnapshot.Manufacturer;
                    }
                    if (firstTimeSeen || aircraftSnapshot.ModelChanged > args.PreviousDataVersion)
                    {
                        aircraftJson.Model = aircraftSnapshot.Model;
                    }
                    if (firstTimeSeen || aircraftSnapshot.NumberOfEnginesChanged > args.PreviousDataVersion)
                    {
                        aircraftJson.NumberOfEngines = aircraftSnapshot.NumberOfEngines;
                    }
                    if (firstTimeSeen || aircraftSnapshot.OperatorChanged > args.PreviousDataVersion)
                    {
                        aircraftJson.Operator = aircraftSnapshot.Operator;
                    }
                    if (firstTimeSeen || aircraftSnapshot.OperatorIcaoChanged > args.PreviousDataVersion)
                    {
                        aircraftJson.OperatorIcao = aircraftSnapshot.OperatorIcao;
                    }
                    if (firstTimeSeen || aircraftSnapshot.OriginChanged > args.PreviousDataVersion)
                    {
                        aircraftJson.Origin = aircraftSnapshot.Origin;
                    }
                    if (firstTimeSeen || aircraftSnapshot.PictureFileNameChanged > args.PreviousDataVersion)
                    {
                        aircraftJson.HasPicture = !String.IsNullOrEmpty(aircraftSnapshot.PictureFileName);
                    }
                    if (firstTimeSeen || aircraftSnapshot.PictureHeightChanged > args.PreviousDataVersion)
                    {
                        aircraftJson.PictureHeight = aircraftSnapshot.PictureHeight == 0 ? (int?)null : aircraftSnapshot.PictureHeight;
                    }
                    if (firstTimeSeen || aircraftSnapshot.PictureWidthChanged > args.PreviousDataVersion)
                    {
                        aircraftJson.PictureWidth = aircraftSnapshot.PictureWidth == 0 ? (int?)null : aircraftSnapshot.PictureWidth;
                    }
                    if (firstTimeSeen || aircraftSnapshot.PositionTimeChanged > args.PreviousDataVersion)
                    {
                        aircraftJson.PositionTime = aircraftSnapshot.PositionTime == null ? (long?)null : JavascriptHelper.ToJavascriptTicks(aircraftSnapshot.PositionTime.Value);
                    }
                    if (firstTimeSeen || aircraftSnapshot.ReceiverIdChanged > args.PreviousDataVersion)
                    {
                        aircraftJson.ReceiverId = aircraftSnapshot.ReceiverId;
                    }
                    if (firstTimeSeen || aircraftSnapshot.RegistrationChanged > args.PreviousDataVersion)
                    {
                        aircraftJson.Registration = aircraftSnapshot.Registration;
                    }
                    if (firstTimeSeen || aircraftSnapshot.SpeciesChanged > args.PreviousDataVersion)
                    {
                        aircraftJson.Species = (int)aircraftSnapshot.Species;
                    }
                    if (firstTimeSeen || aircraftSnapshot.TypeChanged > args.PreviousDataVersion)
                    {
                        aircraftJson.Type = aircraftSnapshot.Type;
                    }
                    if (firstTimeSeen || aircraftSnapshot.UserTagChanged > args.PreviousDataVersion)
                    {
                        aircraftJson.UserTag = aircraftSnapshot.UserTag;
                    }
                    if (firstTimeSeen || aircraftSnapshot.WakeTurbulenceCategoryChanged > args.PreviousDataVersion)
                    {
                        aircraftJson.WakeTurbulenceCategory = (int)aircraftSnapshot.WakeTurbulenceCategory;
                    }
                    if (firstTimeSeen || aircraftSnapshot.YearBuiltChanged > args.PreviousDataVersion)
                    {
                        aircraftJson.YearBuilt = aircraftSnapshot.YearBuilt;
                    }

                    if (aircraftSnapshot.Stopovers.Count > 0 && (firstTimeSeen || aircraftSnapshot.StopoversChanged > args.PreviousDataVersion))
                    {
                        aircraftJson.Stopovers = new List <string>();
                        aircraftJson.Stopovers.AddRange(aircraftSnapshot.Stopovers);
                    }

                    aircraftJson.SecondsTracked  = (long)((now - aircraftSnapshot.FirstSeen).TotalSeconds);
                    aircraftJson.PositionIsStale = aircraftSnapshot.LastSatcomUpdate == DateTime.MinValue &&        // Never flag satcom aircraft as having a stale position
                                                   aircraftSnapshot.PositionTime != null &&
                                                   aircraftSnapshot.PositionTime < positionTimeoutThreshold ? true : (bool?)null;
                }

                if (args.TrailType != TrailType.None)
                {
                    var hasTrail     = false;
                    var isShort      = false;
                    var showAltitude = false;
                    var showSpeed    = false;
                    switch (args.TrailType)
                    {
                    case TrailType.Short:           isShort = true; hasTrail = aircraftSnapshot.ShortCoordinates.Count > 0; break;

                    case TrailType.ShortAltitude:   showAltitude = true; aircraftJson.TrailType = "a"; goto case TrailType.Short;

                    case TrailType.ShortSpeed:      showSpeed = true; aircraftJson.TrailType = "s"; goto case TrailType.Short;

                    case TrailType.Full:            hasTrail = aircraftSnapshot.FullCoordinates.Count > 0; break;

                    case TrailType.FullAltitude:    showAltitude = true; aircraftJson.TrailType = "a"; goto case TrailType.Full;

                    case TrailType.FullSpeed:       showSpeed = true; aircraftJson.TrailType = "s"; goto case TrailType.Full;
                    }
                    if (hasTrail)
                    {
                        BuildCoordinatesList(isShort, firstTimeSeen, aircraftJson, aircraftSnapshot, args, showAltitude, showSpeed);
                    }
                }

                aircraftListJson.Aircraft.Add(aircraftJson);
            }
        }
示例#21
0
        /// <summary>
        /// See interface docs.
        /// </summary>
        /// <param name="utcNow"></param>
        /// <param name="shortCoordinateSeconds"></param>
        public void UpdateCoordinates(DateTime utcNow, int shortCoordinateSeconds)
        {
            if (Latitude != null && Longitude != null)
            {
                var nowTick = utcNow.Ticks;

                var lastFullCoordinate       = FullCoordinates.Count == 0 ? null : FullCoordinates[FullCoordinates.Count - 1];
                var secondLastFullCoordinate = FullCoordinates.Count < 2 ? null : FullCoordinates[FullCoordinates.Count - 2];
                if (lastFullCoordinate == null || Latitude != lastFullCoordinate.Latitude || Longitude != lastFullCoordinate.Longitude)
                {
                    PositionTime = utcNow;

                    // Check to see whether the aircraft appears to be moving impossibly fast and, if it is, reset its trail. Do this even if
                    // the gap between this message and the last is below the threshold for adding to the trails.
                    if (lastFullCoordinate != null)
                    {
                        var distance = GreatCircleMaths.Distance(lastFullCoordinate.Latitude, lastFullCoordinate.Longitude, Latitude, Longitude);
                        if (distance > _ResetCoordinatesDistance)
                        {
                            var fastestTime = _ResetCoordinatesTime * (distance / _ResetCoordinatesDistance);
                            if (nowTick - lastFullCoordinate.Tick < fastestTime)
                            {
                                ResetCoordinates();
                            }
                        }
                    }

                    // Only update the trails if more than one second has elapsed since the last position update
                    long lastUpdateTick = lastFullCoordinate == null ? 0 : lastFullCoordinate.Tick;
                    if (nowTick - lastUpdateTick >= TicksPerSecond)
                    {
                        var coordinate = new Coordinate(DataVersion, nowTick, (float)Latitude, (float)Longitude, Track);

                        if (FullCoordinates.Count > 1 &&
                            (int)(lastFullCoordinate.Heading.GetValueOrDefault() + 0.5f) == (int)(Track.GetValueOrDefault() + 0.5f) &&
                            (int)(secondLastFullCoordinate.Heading.GetValueOrDefault() + 0.5f) == (int)(Track.GetValueOrDefault() + 0.5f))
                        {
                            FullCoordinates[FullCoordinates.Count - 1] = coordinate;
                        }
                        else
                        {
                            FullCoordinates.Add(coordinate);
                        }

                        long earliestAllowable   = nowTick - (TicksPerSecond * shortCoordinateSeconds);
                        var  firstAllowableIndex = ShortCoordinates.FindIndex(c => c.Tick >= earliestAllowable);
                        if (firstAllowableIndex == -1)
                        {
                            ShortCoordinates.Clear();
                        }
                        else if (firstAllowableIndex > 0)
                        {
                            ShortCoordinates.RemoveRange(0, firstAllowableIndex);
                        }
                        ShortCoordinates.Add(coordinate);

                        if (FirstCoordinateChanged == 0)
                        {
                            FirstCoordinateChanged = DataVersion;
                        }
                        LastCoordinateChanged = DataVersion;
                        LatestCoordinateTime  = utcNow;
                    }
                }
            }
        }
示例#22
0
        /// <summary>
        /// Copies the aircraft from the snapshot to the JSON object.
        /// </summary>
        /// <param name="aircraftListJson"></param>
        /// <param name="aircraftListSnapshot"></param>
        /// <param name="args"></param>
        /// <param name="distances"></param>
        private void CopyAircraft(AircraftListJson aircraftListJson, List <IAircraft> aircraftListSnapshot, AircraftListJsonBuilderArgs args, Dictionary <int, double?> distances)
        {
            var now = _Provider.UtcNow;

            foreach (var aircraftSnapshot in aircraftListSnapshot)
            {
                double?distance;
                if (!distances.TryGetValue(aircraftSnapshot.UniqueId, out distance))
                {
                    distance = null;
                }

                var aircraftJson = new AircraftJson()
                {
                    BearingFromHere  = GreatCircleMaths.Bearing(args.BrowserLatitude, args.BrowserLongitude, aircraftSnapshot.Latitude, aircraftSnapshot.Longitude, null, false, true),
                    DistanceFromHere = distance == null ? (double?)null : Math.Round(distance.Value, 2),
                    UniqueId         = aircraftSnapshot.UniqueId,
                };
                if (aircraftJson.BearingFromHere != null)
                {
                    aircraftJson.BearingFromHere = Math.Round(aircraftJson.BearingFromHere.Value, 1);
                }

                bool firstTimeSeen = !args.PreviousAircraft.Contains(aircraftSnapshot.UniqueId);

                if (firstTimeSeen || aircraftSnapshot.AltitudeChanged > args.PreviousDataVersion)
                {
                    aircraftJson.Altitude = aircraftSnapshot.Altitude;
                }
                if (firstTimeSeen || aircraftSnapshot.CallsignChanged > args.PreviousDataVersion)
                {
                    aircraftJson.Callsign = aircraftSnapshot.Callsign;
                }
                if (firstTimeSeen || aircraftSnapshot.CallsignIsSuspectChanged > args.PreviousDataVersion)
                {
                    aircraftJson.CallsignIsSuspect = aircraftSnapshot.CallsignIsSuspect;
                }
                if (firstTimeSeen || aircraftSnapshot.ConstructionNumberChanged > args.PreviousDataVersion)
                {
                    aircraftJson.ConstructionNumber = aircraftSnapshot.ConstructionNumber;
                }
                if (firstTimeSeen || aircraftSnapshot.CountMessagesReceivedChanged > args.PreviousDataVersion)
                {
                    aircraftJson.CountMessagesReceived = aircraftSnapshot.CountMessagesReceived;
                }
                if (firstTimeSeen || aircraftSnapshot.DestinationChanged > args.PreviousDataVersion)
                {
                    aircraftJson.Destination = aircraftSnapshot.Destination;
                }
                if (firstTimeSeen || aircraftSnapshot.GroundSpeedChanged > args.PreviousDataVersion)
                {
                    aircraftJson.GroundSpeed = Round.GroundSpeed(aircraftSnapshot.GroundSpeed);
                }
                if (firstTimeSeen || aircraftSnapshot.EmergencyChanged > args.PreviousDataVersion)
                {
                    aircraftJson.Emergency = aircraftSnapshot.Emergency;
                }
                if (firstTimeSeen || aircraftSnapshot.EngineTypeChanged > args.PreviousDataVersion)
                {
                    aircraftJson.EngineType = (int)aircraftSnapshot.EngineType;
                }
                if (firstTimeSeen || aircraftSnapshot.FirstSeenChanged > args.PreviousDataVersion)
                {
                    aircraftJson.FirstSeen = aircraftSnapshot.FirstSeen;
                }
                if (firstTimeSeen || aircraftSnapshot.FlightsCountChanged > args.PreviousDataVersion)
                {
                    aircraftJson.FlightsCount = aircraftSnapshot.FlightsCount;
                }
                if (firstTimeSeen || aircraftSnapshot.PictureFileNameChanged > args.PreviousDataVersion)
                {
                    aircraftJson.HasPicture = !String.IsNullOrEmpty(aircraftSnapshot.PictureFileName);
                }
                if (firstTimeSeen || aircraftSnapshot.Icao24Changed > args.PreviousDataVersion)
                {
                    aircraftJson.Icao24 = aircraftSnapshot.Icao24;
                }
                if (firstTimeSeen || aircraftSnapshot.Icao24CountryChanged > args.PreviousDataVersion)
                {
                    aircraftJson.Icao24Country = aircraftSnapshot.Icao24Country;
                }
                if (firstTimeSeen || aircraftSnapshot.Icao24InvalidChanged > args.PreviousDataVersion)
                {
                    aircraftJson.Icao24Invalid = aircraftSnapshot.Icao24Invalid;
                }
                if (firstTimeSeen || aircraftSnapshot.IsMilitaryChanged > args.PreviousDataVersion)
                {
                    aircraftJson.IsMilitary = aircraftSnapshot.IsMilitary;
                }
                if (firstTimeSeen || aircraftSnapshot.IsInterestingChanged > args.PreviousDataVersion)
                {
                    aircraftJson.IsInteresting = aircraftSnapshot.IsInteresting;
                }
                if (firstTimeSeen || aircraftSnapshot.LatitudeChanged > args.PreviousDataVersion)
                {
                    aircraftJson.Latitude = Round.Coordinate(aircraftSnapshot.Latitude);
                }
                if (firstTimeSeen || aircraftSnapshot.LongitudeChanged > args.PreviousDataVersion)
                {
                    aircraftJson.Longitude = Round.Coordinate(aircraftSnapshot.Longitude);
                }
                if (firstTimeSeen || aircraftSnapshot.ModelChanged > args.PreviousDataVersion)
                {
                    aircraftJson.Model = aircraftSnapshot.Model;
                }
                if (firstTimeSeen || aircraftSnapshot.NumberOfEnginesChanged > args.PreviousDataVersion)
                {
                    aircraftJson.NumberOfEngines = aircraftSnapshot.NumberOfEngines;
                }
                if (firstTimeSeen || aircraftSnapshot.OnGroundChanged > args.PreviousDataVersion)
                {
                    aircraftJson.OnGround = aircraftSnapshot.OnGround;
                }
                if (firstTimeSeen || aircraftSnapshot.OperatorChanged > args.PreviousDataVersion)
                {
                    aircraftJson.Operator = aircraftSnapshot.Operator;
                }
                if (firstTimeSeen || aircraftSnapshot.OperatorIcaoChanged > args.PreviousDataVersion)
                {
                    aircraftJson.OperatorIcao = aircraftSnapshot.OperatorIcao;
                }
                if (firstTimeSeen || aircraftSnapshot.OriginChanged > args.PreviousDataVersion)
                {
                    aircraftJson.Origin = aircraftSnapshot.Origin;
                }
                if (firstTimeSeen || aircraftSnapshot.PositionTimeChanged > args.PreviousDataVersion)
                {
                    aircraftJson.PositionTime = aircraftSnapshot.PositionTime == null ? (long?)null : JavascriptHelper.ToJavascriptTicks(aircraftSnapshot.PositionTime.Value);
                }
                if (firstTimeSeen || aircraftSnapshot.RegistrationChanged > args.PreviousDataVersion)
                {
                    aircraftJson.Registration = aircraftSnapshot.Registration;
                }
                if (firstTimeSeen || aircraftSnapshot.SpeciesChanged > args.PreviousDataVersion)
                {
                    aircraftJson.Species = (int)aircraftSnapshot.Species;
                }
                if (firstTimeSeen || aircraftSnapshot.SpeedTypeChanged > args.PreviousDataVersion)
                {
                    aircraftJson.SpeedType = (int)aircraftSnapshot.SpeedType;
                }
                if (firstTimeSeen || aircraftSnapshot.SquawkChanged > args.PreviousDataVersion)
                {
                    aircraftJson.Squawk = String.Format("{0:0000}", aircraftSnapshot.Squawk);
                }
                if (firstTimeSeen || aircraftSnapshot.TrackChanged > args.PreviousDataVersion)
                {
                    aircraftJson.Track = Round.Track(aircraftSnapshot.Track);
                }
                if (firstTimeSeen || aircraftSnapshot.TypeChanged > args.PreviousDataVersion)
                {
                    aircraftJson.Type = aircraftSnapshot.Type;
                }
                if (firstTimeSeen || aircraftSnapshot.UserTagChanged > args.PreviousDataVersion)
                {
                    aircraftJson.UserTag = aircraftSnapshot.UserTag;
                }
                if (firstTimeSeen || aircraftSnapshot.VerticalRateChanged > args.PreviousDataVersion)
                {
                    aircraftJson.VerticalRate = aircraftSnapshot.VerticalRate;
                }
                if (firstTimeSeen || aircraftSnapshot.WakeTurbulenceCategoryChanged > args.PreviousDataVersion)
                {
                    aircraftJson.WakeTurbulenceCategory = (int)aircraftSnapshot.WakeTurbulenceCategory;
                }

                if (aircraftSnapshot.Stopovers.Count > 0 && (firstTimeSeen || aircraftSnapshot.StopoversChanged > args.PreviousDataVersion))
                {
                    aircraftJson.Stopovers = new List <string>();
                    aircraftJson.Stopovers.AddRange(aircraftSnapshot.Stopovers);
                }

                aircraftJson.SecondsTracked = (long)((now - aircraftSnapshot.FirstSeen).TotalSeconds);

                if (args.ShowShortTrail)
                {
                    if (aircraftSnapshot.ShortCoordinates.Count > 0)
                    {
                        BuildCoordinatesList(true, firstTimeSeen, aircraftJson, aircraftSnapshot, args);
                    }
                }
                else
                {
                    if (aircraftSnapshot.FullCoordinates.Count > 0)
                    {
                        BuildCoordinatesList(false, firstTimeSeen, aircraftJson, aircraftSnapshot, args);
                    }
                }

                aircraftListJson.Aircraft.Add(aircraftJson);
            }
        }