diff --git a/src/AprsParser/Position.cs b/src/AprsParser/Position.cs
index c92b844..b51d2e7 100644
--- a/src/AprsParser/Position.cs
+++ b/src/AprsParser/Position.cs
@@ -1,6 +1,7 @@
namespace AprsSharp.AprsParser
{
using System;
+ using System.Diagnostics;
using System.Globalization;
using System.Text;
using System.Text.RegularExpressions;
@@ -167,16 +168,63 @@ public void Decode(string coords)
throw new ArgumentNullException(nameof(coords));
}
+ Ambiguity = 0;
+
+ // first try uncompressed
Match match = Regex.Match(coords, RegexStrings.PositionLatLongWithSymbols);
- match.AssertSuccess("Coordinates", nameof(coords));
- Ambiguity = 0;
- double latitude = DecodeLatitude(match.Groups[1].Value);
- double longitude = DecodeLongitude(match.Groups[3].Value);
+ if (match.Success)
+ {
+ double latitude = DecodeLatitude(match.Groups[1].Value);
+ double longitude = DecodeLongitude(match.Groups[3].Value);
- SymbolTableIdentifier = match.Groups[2].Value[0];
- SymbolCode = match.Groups[4].Value[0];
- Coordinates = new GeoCoordinate(latitude, longitude);
+ SymbolTableIdentifier = match.Groups[2].Value[0];
+ SymbolCode = match.Groups[4].Value[0];
+ Coordinates = new GeoCoordinate(latitude, longitude);
+ return;
+ }
+
+ // next try compressed
+ match = Regex.Match(coords, RegexStrings.CompressedPosition);
+
+ if (match.Success)
+ {
+ double latitude = DecodeCompressedLatitude(match.Groups[2].Value);
+ double longitude = DecodeCompressedLongitude(match.Groups[3].Value);
+ SymbolTableIdentifier = match.Groups[1].Value[0];
+ SymbolCode = match.Groups[4].Value[0];
+ Coordinates = new GeoCoordinate(latitude, longitude);
+ return;
+ }
+ }
+
+ private static int DecodeBase91(string encoded)
+ {
+ var bytes = Encoding.ASCII.GetBytes(encoded);
+
+ var result = 0;
+ for (var i = 0; i < bytes.Length; i++)
+ {
+ result += i == bytes.Length - 1
+ ? bytes[i] - 33
+ : (bytes[i] - 33) * (int)Math.Pow(91, bytes.Length - i - 1);
+ }
+
+ return result;
+ }
+
+ private static double DecodeCompressedLatitude(string coords)
+ {
+ Debug.Assert(coords.Length == 4, "Compressed latitude must be 4 characters");
+ var latitude = 90 - (DecodeBase91(coords) / 380926.0);
+ return Math.Round(latitude, 4);
+ }
+
+ private static double DecodeCompressedLongitude(string coords)
+ {
+ Debug.Assert(coords.Length == 4, "Compressed longitude must be 4 characters");
+ var longitude = -180 + (DecodeBase91(coords) / 190463.0);
+ return Math.Round(longitude, 4);
}
///
diff --git a/src/AprsParser/RegexStrings.cs b/src/AprsParser/RegexStrings.cs
index 8023807..512a99a 100644
--- a/src/AprsParser/RegexStrings.cs
+++ b/src/AprsParser/RegexStrings.cs
@@ -34,6 +34,18 @@ internal static class RegexStrings
///
public const string PositionLatLongWithSymbols = @"([0-9 \.NS]{8})(.)([0-9 \.EW]{9})(.)";
+ ///
+ /// Matchdes a compressed latitude and longitude with optional additional data
+ /// Six matches:
+ /// Symbol table ID
+ /// Compressed Latitude
+ /// Compressed Longitude
+ /// Symbol code
+ /// Compressed one of: course/speed, radio range, or altitude
+ /// Compressed data type
+ ///
+ public const string CompressedPosition = @"(.)(.{4})(.{4})(.)(.{2})(.)";
+
///
/// Same as but forces full line match.
///
diff --git a/test/AprsParserUnitTests/PositionUnitTests.cs b/test/AprsParserUnitTests/PositionUnitTests.cs
index c88ea86..f187d2c 100644
--- a/test/AprsParserUnitTests/PositionUnitTests.cs
+++ b/test/AprsParserUnitTests/PositionUnitTests.cs
@@ -293,6 +293,7 @@ public void DecodeLongitudeWrongDecimalPointLocation()
[InlineData(null, '\\', '.', 0, 0)] // defaults
[InlineData("4903.50N/07201.75W-", '/', '-', 49.0583, -72.0292)] // from APRS spec
[InlineData("4903.50S/07201.75E-", '/', '-', -49.0583, 72.0292)] // Ensure south and east work
+ [InlineData("/5L!!<*e7>7P[", '/', '>', 49.5, -72.7500)] // from the APRS spec
public void Decode(
string? encodedPosition,
char expectedSymbolTable,