public static AprsMessage Parse(string rawData) { try { var match = Constants.Regexes.CallsignRegex.Match(rawData); var aprsMessage = new AprsMessage { RawData = rawData, ReceivedDate = DateTime.UtcNow, Callsign = match.Groups[1].Value }; rawData = match.Groups[2].Value; match = Regex.Match(rawData, @"^([^\:]*)\:(.*)$"); aprsMessage.StationRoute = new ReadOnlyCollection <string>(match.Groups[1].Value.Split(',')); rawData = match.Groups[2].Value; if (string.IsNullOrEmpty(rawData)) { return(null); } DataType dataType; if (!Constants.Maps.DataTypeMap.TryGetValue(Convert.ToByte(rawData[0]), out dataType)) { throw new ArgumentException("Unsupported data type in raw data", nameof(rawData)); } aprsMessage.DataType = dataType; Symbol symbol; switch (aprsMessage.DataType) { case DataType.CurrentMicE: Latitude latitude; short longitudeOffset; LongitudeHemisphere longitudeHemisphere; MicEMessageType micEMessageType; DecodeMicEDestinationAddress( aprsMessage.StationRoute[0], out latitude, out longitudeOffset, out longitudeHemisphere, out micEMessageType); aprsMessage.Latitude = latitude; aprsMessage.MicEMessageType = micEMessageType; var longitudeDegrees = (short)(rawData[1] - 28 + longitudeOffset); if (180 <= longitudeDegrees && longitudeDegrees <= 189) { longitudeDegrees -= 80; } else if (190 <= longitudeDegrees && longitudeDegrees <= 199) { longitudeDegrees -= 190; } aprsMessage.Longitude = new Longitude( longitudeDegrees, (short)((rawData[2] - 28) % 60), (rawData[3] - 28) * 0.6, longitudeHemisphere, latitude.Ambiguity); var speedCourseSharedByte = rawData[5] - 28; aprsMessage.Speed = Speed.FromKnots(((rawData[4] - 28) * 10 + (int)Math.Floor(speedCourseSharedByte / 10.0)) % 800); aprsMessage.Direction = new Heading((short)((speedCourseSharedByte % 10 * 100 + (rawData[6] - 28)) % 400), 0, 0); { var symbolTableSelector = rawData[8]; if (symbolTableSelector != '/' && symbolTableSelector != '\\') { aprsMessage.SymbolOverlay = symbolTableSelector; aprsMessage.SymbolTable = Constants.Maps.SymbolTableMap['\\']; //Take the secondary table } else { aprsMessage.SymbolTable = Constants.Maps.SymbolTableMap[symbolTableSelector]; } } if ((aprsMessage.SymbolTable == SymbolTable.Primary ? Constants.Maps.PrimarySymbolTableSymbolMap : Constants.Maps.SecondarySymbolTableSymbolMap).TryGetValue(rawData[7], out symbol)) { aprsMessage.Symbol = symbol; } else { aprsMessage.Symbol = null; } if (rawData.Length > 12 && rawData[12] == '}') { aprsMessage.Altitude = Altitude.FromMetersAboveBaseline(ConvertFromBase91(rawData.Substring(9, 3))); } break; case DataType.PositionWithoutTimestampWithAprsMessaging: case DataType.PositionWithoutTimestampNoAprsMessaging: aprsMessage.Latitude = new Latitude(Convert.ToInt16(rawData.Substring(1, 2)), Convert.ToDouble(rawData.Substring(3, 5)), rawData[8] == 'N' ? LatitudeHemisphere.North : LatitudeHemisphere.South); { var symbolTableSelector = rawData[9]; if (symbolTableSelector != '/' && symbolTableSelector != '\\') { aprsMessage.SymbolOverlay = symbolTableSelector; aprsMessage.SymbolTable = Constants.Maps.SymbolTableMap['\\']; //Take the secondary table } else { aprsMessage.SymbolTable = Constants.Maps.SymbolTableMap[symbolTableSelector]; } } aprsMessage.Longitude = new Longitude(Convert.ToInt16(rawData.Substring(10, 3)), Convert.ToDouble(rawData.Substring(13, 5)), rawData[18] == 'E' ? LongitudeHemisphere.East : LongitudeHemisphere.West); if ((aprsMessage.SymbolTable == SymbolTable.Primary ? Constants.Maps.PrimarySymbolTableSymbolMap : Constants.Maps.SecondarySymbolTableSymbolMap).TryGetValue(rawData[19], out symbol)) { aprsMessage.Symbol = symbol; } else { aprsMessage.Symbol = null; } rawData = rawData.Substring(20); if (Regex.IsMatch(rawData, @"^\d\d\d\\\d\d\d")) { aprsMessage.Direction = new Heading(Convert.ToInt16(rawData.Substring(0, 3)), 0, 0); aprsMessage.Speed = Speed.FromKnots(Convert.ToDouble(rawData.Substring(4, 3))); rawData = rawData.Substring(7); } if (Constants.Regexes.AltitudeRegex.IsMatch(rawData)) { aprsMessage.Altitude = Altitude.FromFeetAboveSeaLevel( Convert.ToInt32(Constants.Regexes.AltitudeRegex.Match(rawData).Groups[1].Value)); } // OGN FLAVORED STUFF if (Regex.IsMatch(rawData, @"([\+\-]\d\d\d)fpm")) { aprsMessage.ClimbRate = Convert.ToInt16(Regex.Match(rawData, @"([\+\-]\d\d\d)fpm").Groups[1].Value); } if (Regex.IsMatch(rawData, @"([\+\-]\d.\d)rot")) { aprsMessage.TurnRate = Convert.ToDouble(Regex.Match(rawData, @"([\+\-]\d.\d)rot").Groups[1].Value); } // ToDo: In case OGN flavored aprs is enabled add the precision enhancement, clibrate and turnrate. break; case DataType.PositionWithTimestampWithAprsMessaging: case DataType.PositionWithTimestampNoAprsMessaging: if (Regex.IsMatch(rawData.Substring(1), @"^(\d\d\d\d\d\d)h")) { rawData = rawData.Substring(8); } else { return(null); } aprsMessage.Latitude = new Latitude(Convert.ToInt16(rawData.Substring(0, 2)), Convert.ToDouble(rawData.Substring(2, 5)), rawData[7] == 'N' ? LatitudeHemisphere.North : LatitudeHemisphere.South); SymbolTable symbolTable; if (!Constants.Maps.SymbolTableMap.TryGetValue(rawData[8], out symbolTable)) { return(null); } aprsMessage.SymbolTable = symbolTable; aprsMessage.Longitude = new Longitude(Convert.ToInt16(rawData.Substring(9, 3)), Convert.ToDouble(rawData.Substring(12, 5)), rawData[17] == 'E' ? LongitudeHemisphere.East : LongitudeHemisphere.West); if ((aprsMessage.SymbolTable == SymbolTable.Primary ? Constants.Maps.PrimarySymbolTableSymbolMap : Constants.Maps.SecondarySymbolTableSymbolMap).TryGetValue(rawData[18], out symbol)) { aprsMessage.Symbol = symbol; } else { aprsMessage.Symbol = null; } short direction; if (!Int16.TryParse(rawData.Substring(19, 3), out direction)) { return(null); } aprsMessage.Direction = new Heading(direction, 0, 0); aprsMessage.Speed = Speed.FromKnots(Convert.ToDouble(rawData.Substring(23, 3))); rawData = rawData.Substring(26); if (Constants.Regexes.AltitudeRegex.IsMatch(rawData)) { aprsMessage.Altitude = Altitude.FromFeetAboveSeaLevel( Convert.ToInt32(Constants.Regexes.AltitudeRegex.Match(rawData).Groups[1].Value)); } // OGN FLAVORED STUFF if (Regex.IsMatch(rawData, @"([\+\-]\d\d\d)fpm")) { aprsMessage.ClimbRate = Convert.ToInt16(Regex.Match(rawData, @"([\+\-]\d\d\d)fpm").Groups[1].Value); } if (Regex.IsMatch(rawData, @"([\+\-]\d.\d)rot")) { aprsMessage.TurnRate = Convert.ToDouble(Regex.Match(rawData, @"([\+\-]\d.\d)rot").Groups[1].Value); } break; } return(aprsMessage); } catch { return(null); } }