/// <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); }
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); } } }
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); } }
/// <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); }
/// <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); }
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); } } } }
/// <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); }
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); }
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)); }
/// <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); }
/// <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); }
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); } }
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); } }
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); } } } }
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); } }
/// <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; } } } } } } }
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)); } } }
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 }
/// <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); }
/// <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); } }
/// <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; } } } }
/// <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); } }