public void CompactPositionReportingCoordinate_Constructor_Initialises_To_Known_State() { var coordinate = new CompactPositionReportingCoordinate(1, 2, true, 4); Assert.AreEqual(1, coordinate.Latitude); Assert.AreEqual(2, coordinate.Longitude); Assert.AreEqual(true, coordinate.OddFormat); Assert.AreEqual((byte)4, coordinate.NumberOfBits); }
public void CompactPositionReportingCoordinate_Default_Constructor_Initialises_To_Known_State_And_Properties_Work() { var coordinate = new CompactPositionReportingCoordinate(); TestUtilities.TestProperty(coordinate, r => r.Latitude, 0, 0x10000); TestUtilities.TestProperty(coordinate, r => r.Longitude, 0, 0x10000); TestUtilities.TestProperty(coordinate, r => r.OddFormat, false); TestUtilities.TestProperty(coordinate, r => r.NumberOfBits, (byte)0, (byte)19); }
/// <summary> /// Extracts the fields describing a Compact Position Reporting coordinate in a message. /// </summary> /// <param name="message"></param> /// <param name="encodingBits"></param> /// <returns></returns> private CompactPositionReportingCoordinate ExtractCprCoordinate(AdsbMessage message, byte encodingBits) { CompactPositionReportingCoordinate result = null; if(message.Type != 0) { bool isOddFormat = _BitStream.ReadBit(); int latitude = (int)_BitStream.ReadUInt32(17); int longitude = (int)_BitStream.ReadUInt32(17); result = new CompactPositionReportingCoordinate(latitude, longitude, isOddFormat, encodingBits); } return result; }
/// <summary> /// See interface docs. /// </summary> /// <param name="cprCoordinate"></param> /// <param name="referenceCoordinate"></param> /// <returns></returns> /// <remarks><para> /// It was found in testing that encoded latitudes of zero are incredibly sensitive to rounding errors introduced by the use of doubles. This /// can be fixed by using decimals, which have much greater precision, and rounding to a very large number of decimal places - but using decimals /// is bad news, they are a lot slower than doubles because all of the operations are done in software, there is no hardware FPU support for them. /// 10 million divisions in LINQPad took 58 seconds on my development machine when using decimals and less than a second using doubles. /// </para><para> /// However it's only on the boundary between zones where rounding errors cause huge problems (e.g. returning a longitude of -87 instead of -90). /// The problem revolves around the selection of the wrong value of m, rounding errors in doubles at boundaries between zones can push m out by /// one, placing the longitude into the wrong zone and getting a longitude that it out by miles. Once the longitude is above zero rounding errors /// can have an effect but the correct m is still selected and they only produce inaccuracies of a few feet. So the compromise was to use decimals /// when accuracy is paramount - i.e. when the encoded longitude is 0 - and doubles for the rest of the time. /// </para></remarks> public GlobalCoordinate LocalDecode(CompactPositionReportingCoordinate cprCoordinate, GlobalCoordinate referenceCoordinate) { var result = new GlobalCoordinate(); var maxResult = MaxResultSize(cprCoordinate.NumberOfBits == 19 ? 17 : cprCoordinate.NumberOfBits); var dlat = cprCoordinate.NumberOfBits == 19 ? cprCoordinate.OddFormat ? DLatOdd19Bit : DLatEven19Bit : cprCoordinate.OddFormat ? DLatOdd : DLatEven; var dlonNumerator = cprCoordinate.NumberOfBits == 19 ? 90.0 : 360.0; var refLat = referenceCoordinate.Latitude; var encodedLatitudeSlice = (double)cprCoordinate.Latitude / maxResult; var j = Math.Floor(refLat / dlat) + Math.Floor(0.5 + (CircleModulus(refLat, dlat) / dlat) - encodedLatitudeSlice); result.Latitude = dlat * (j + encodedLatitudeSlice); var oddEvenNL = NL(result.Latitude) - (cprCoordinate.OddFormat ? 1 : 0); if(cprCoordinate.Longitude != 0) { // The version that uses doubles for speed at the expense of potential rounding errors. Any changes here need duplicating below! var refLon = referenceCoordinate.Longitude; var dlon = oddEvenNL == 0 ? dlonNumerator : dlonNumerator / oddEvenNL; var encodedLongitudeSlice = (double)cprCoordinate.Longitude / maxResult; var m = Math.Floor(refLon / dlon) + Math.Floor(0.5 + (CircleModulus(refLon, dlon) / dlon) - encodedLongitudeSlice); result.Longitude = dlon * (m + encodedLongitudeSlice); } else { // Same as the block above but uses decimals for accuracy at the expense of speed. It would be nice if I could use C# generic functions // to remove this redundancy, but they're not very good for that. Sometimes I miss C++, I'd have 1000 ways to make the code common :) // Anyway, any changes here need duplicating above! var refLon = (decimal)referenceCoordinate.Longitude; var dlon = oddEvenNL == 0 ? (decimal)dlonNumerator : (decimal)dlonNumerator / oddEvenNL; var encodedLongitudeSlice = (decimal)cprCoordinate.Longitude / maxResult; var m = Math.Floor(Math.Round(refLon / dlon, 20)) + Math.Floor(0.5M + (CircleModulus(refLon, dlon) / dlon) - encodedLongitudeSlice); result.Longitude = (double)(dlon * (m + encodedLongitudeSlice)); } return result; }
public TrackedAircraftState State; // The current stage of position acquisition for the vehicle. #endregion Fields #region Methods public void RecordMessage(DateTime time, CompactPositionReportingCoordinate cpr) { EarlierCpr = LaterCpr; LaterCpr = new PositionMessage() { Time = time, Cpr = cpr }; }
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> /// See interface docs. /// </summary> /// <param name="earlyCpr"></param> /// <param name="laterCpr"></param> /// <param name="receiverLocation"></param> /// <returns></returns> public GlobalCoordinate GlobalDecode(CompactPositionReportingCoordinate earlyCpr, CompactPositionReportingCoordinate laterCpr, GlobalCoordinate receiverLocation) { GlobalCoordinate result = null; if(earlyCpr != null && laterCpr != null && earlyCpr.OddFormat != laterCpr.OddFormat && earlyCpr.NumberOfBits == laterCpr.NumberOfBits && (laterCpr.NumberOfBits < 19 || receiverLocation != null)) { var numberOfBits = earlyCpr.NumberOfBits; var surfaceEncoding = numberOfBits == 19; var dlatOdd = surfaceEncoding ? DLatOdd19Bit : DLatOdd; var dlatEven = surfaceEncoding ? DLatEven19Bit : DLatEven; var maxResult = MaxResultSize(surfaceEncoding ? 17 : numberOfBits); var oddCoordinate = earlyCpr.OddFormat ? earlyCpr : laterCpr; var evenCoordinate = oddCoordinate == earlyCpr ? laterCpr : earlyCpr; var j = Math.Floor(((59 * (double)evenCoordinate.Latitude - 60 * (double)oddCoordinate.Latitude) / maxResult) + 0.5); var rlatEven = dlatEven * (CircleModulus(j, 60) + ((double)evenCoordinate.Latitude / maxResult)); var rlatOdd = dlatOdd * (CircleModulus(j, 59) + ((double)oddCoordinate.Latitude / maxResult)); if(rlatEven >= 270.0) rlatEven -= 360.0; if(rlatOdd >= 270.0) rlatOdd -= 360.0; var rlat = laterCpr.OddFormat ? rlatOdd : rlatEven; if(surfaceEncoding && AbsoluteDifference(rlat, receiverLocation.Latitude) > AbsoluteDifference(rlat - 90.0, receiverLocation.Latitude)) { rlat -= 90.0; rlatEven -= 90.0; rlatOdd -= 90.0; } var nlEven = NL(rlatEven); var nlOdd = NL(rlatOdd); if(nlEven == nlOdd) { var nl = nlEven; var mNumerator = (double)evenCoordinate.Longitude * (nl - 1) - (double)oddCoordinate.Longitude * nl; var m = Math.Floor((mNumerator / maxResult) + 0.5); var ni = Math.Max(nl - (laterCpr.OddFormat ? 1 : 0), 1); var dlon = (surfaceEncoding ? 90.0 : 360.0) / ni; var rlon = dlon * (CircleModulus(m, ni) + ((double)laterCpr.Longitude / maxResult)); if(!surfaceEncoding) rlon = CircleModulus(rlon + 180.0, 360.0) - 180.0; else { var deltaDegrees = double.MaxValue; var receiverBearing = LongitudeToBearing(receiverLocation.Longitude); foreach(var possibleRlon in new double[] { rlon, rlon - 90, rlon - 180, rlon - 270 }) { var adjustedRlon = CircleModulus(possibleRlon + 180.0, 360.0) - 180.0; var rlonBearing = LongitudeToBearing(adjustedRlon); var delta = SmallestDegreesBetweenBearings(rlonBearing, receiverBearing); if(delta < 0.0) delta += 360.0; if(delta < deltaDegrees) { rlon = adjustedRlon; deltaDegrees = delta; } } } result = new GlobalCoordinate(rlat, rlon); } } return result; }
public void CompactPositionReporting_GlobalDecode_Returns_Null_If_No_Receiver_Location_Is_Supplied_For_19Bit_Format() { var message1 = new CompactPositionReportingCoordinate(130929, 23302, false, 19); // {38.998363494873/-73.9999953560207} var message2 = new CompactPositionReportingCoordinate(74133, 0, true, 19); // {38.998357, -74.0} Assert.IsNull(_Cpr.GlobalDecode(message1, message2, null)); }
public void CompactPositionReporting_GlobalDecode_Calculates_1090_WP30_17_Test_Surface_Positions() { var message1 = new CompactPositionReportingCoordinate(130929, 23302, false, 19); // {38.998363494873/-73.9999953560207} var message2 = new CompactPositionReportingCoordinate(74133, 0, true, 19); // {38.998357, -74.0} var decoded = _Cpr.GlobalDecode(message1, message2, new GlobalCoordinate(38.99836, -74)); Assert.AreEqual(38.998357, decoded.Latitude, 0.000001); Assert.AreEqual(-74.0, decoded.Longitude, 0.000001); var message3 = new CompactPositionReportingCoordinate(0, 23302, false, 19); decoded = _Cpr.GlobalDecode(message2, message3, new GlobalCoordinate(39.0, -74.0)); Assert.AreEqual(39.0, decoded.Latitude, 0.000001); Assert.AreEqual(-73.999995, decoded.Longitude, 0.000001); }
public void CompactPositionReporting_GlobalDecode_Calculates_Correct_Position_For_Internet_Example_1() { var earlyCpr = new CompactPositionReportingCoordinate(92095, 39846, false, 17); var laterCpr = new CompactPositionReportingCoordinate(88385, 125818, true, 17); var decoded = _Cpr.GlobalDecode(earlyCpr, laterCpr, null); Assert.AreEqual(10.2162144547802, decoded.Latitude, 0.00001); Assert.AreEqual(123.889128586342 , decoded.Longitude, 0.00001); }
public void CompactPositionReporting_GlobalDecode_Returns_Null_If_Coordinate_Bits_Are_Different() { var earlier = new CompactPositionReportingCoordinate(1234, 0, true, 17); var later = new CompactPositionReportingCoordinate(1235, 0, false, 19); Assert.IsNull(_Cpr.GlobalDecode(earlier, later, null)); }
public void CompactPositionReporting_GlobalDecode_Returns_Null_If_Either_Coordinate_Is_Null() { var coordinate = new CompactPositionReportingCoordinate(0x10000, 0, true, 17); Assert.IsNull(_Cpr.GlobalDecode(null, coordinate, null)); Assert.IsNull(_Cpr.GlobalDecode(coordinate, null, null)); }