//////////////////////////////////////////////////////////////// // // Copyright (c) 2007-2010 MetaGeek, LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////// // =================================================================== // This code was originally written by: // Joel Carpenter // Brisbane, QLD // AUSTRALIA // =================================================================== using System; using System.Text; using System.Globalization; using System.Collections; using System.Collections.Generic; namespace Inssider { public class Satellite { public int ID; public double Elevation; public double Azimuth; public double SNR; } public class NmeaParser { /// /// Enumerated type of GPS sentences /// public enum SentenceType { NONE = 0, GPRMC, GPGSV, GPGSA, GPGGA, GPVTG } #region Private Data // Represents the EN-US culture, used for numbers in NMEA sentences public static CultureInfo NmeaCultureInfo = CultureInfo.InvariantCulture; // Used to convert knots into km per hour public static double KPHPerKnot = 1.8519984; private char[] delimiters = { ',', '*' }; private double _longitude = 0; private double _latitude = 0; private double _speedKph = 0; private DateTime _timestamp = DateTime.Now; private DateTime _satelliteTime = DateTime.Now; private double _course = 0; private bool _hasFix = false; private double _magVar = double.NaN; private int _satelliteCount = 0; private bool _allSatellitesLoaded = false; //GPGSA private bool _isForced2D3D; private int _fixMode = 0; private int _maxChannels = 12; private int[] _satIDs; private double _PDOP; private double _HDOP; private double _VDOP; //GPGGA private int _satellitesUsed = 0; private int _positionFix = 0; private double _altitude = 0; private double _DGPSAge = 0; private int _DGPSID = 0; private double _geoIdSeperation = 0; private List _satellites = new List(); #endregion public double Longitude { get { return _longitude; } } public double Latitude { get { return _latitude; } } public double Speed { get { return _speedKph; } } public DateTime Timestamp { get { return _timestamp; } } public DateTime SatelliteTime { get { return _satelliteTime; } } public double Course { get { return _course; } } public bool HasFix { get { return _hasFix; } } public double MagVar { get { return _magVar; } } public List Satellites { get { return _satellites; } } public int SatelliteCount { get { return _satelliteCount; } } public bool getAllSatellitesLoaded() { return _allSatellitesLoaded; } public int[] getSatIDs() { return _satIDs; } public int FixMode { get { return _fixMode; } } public bool IsForced2D3D { get { return _isForced2D3D; } } public double PDOP { get { return _PDOP; } } public double VDOP { get { return _VDOP; } } public double HDOP { get { return _HDOP; } } public int SatellitesUsed { get { return _satellitesUsed; } } public int PositionFix { get { return _positionFix; } } public double Altitude { get { return _altitude; } } public double GeoIdSeperation { get { return _geoIdSeperation; } } public double DGPSAge { get { return _DGPSAge; } } public int DGPSID { get { return _DGPSID; } } public string FixString { get { string fix = string.Empty; if (_positionFix <= 1) { switch (_fixMode) { case 1: fix = "none"; break; case 2: fix = "2d"; break; case 3: fix = "3d"; break; default: fix = "none"; break; } } else { switch (_positionFix) { case 2: fix = "dgps"; break; case 3: fix = "pps"; break; default: fix = "none"; break; } } return fix; } } public SentenceType Parse(string sentence) { SentenceType type = SentenceType.NONE; // Discard the sentence if its checksum does not match our calculated checksum if (IsValidSentence(sentence)) { bool result = false; // Look at the first word to decide where to go next switch (GetWords(sentence)[0]) { case "$GPRMC": // A "Recommended Minimum" sentence was found! result = ParseGPRMC(sentence); if (result) { type = SentenceType.GPRMC; } break; case "$GPGSV": // A "Satellites in View" sentence was received result = ParseGPGSV(sentence); if (result) { type = SentenceType.GPGSV; } break; case "$GPGSA": result = ParseGPGSA(sentence); if (result) { type = SentenceType.GPGSA; } break; // Fix Data case "$GPGGA": result = ParseGPGGA(sentence); if (result) { type = SentenceType.GPGGA; } break; case "$GPVTG": result = ParseGPVTG(sentence); if (result) { type = SentenceType.GPVTG; } break; default: // Indicate that the sentence was not recognized break; } } return type; } // Divides a sentence into individual words public string[] GetWords(string sentence) { return sentence.Split(delimiters); } // Interprets a $GPRMC message public bool ParseGPRMC(string sentence) { string[] Words = GetWords(sentence); string rawUTCtime = Words[1]; string rawStatus = Words[2]; string rawLatitude = Words[3]; string rawNSindicator = Words[4]; string rawLongitude = Words[5]; string rawEWindicator = Words[6]; string rawSpeedinKnots = Words[7]; string rawCourse = Words[8]; string rawUTCdate = Words[9]; string rawMagneticVariationDegrees = Words[10]; string rawMagneticVariationEW = Words[11]; string rawChecksum = Words[12]; //GET OUR POSITION. LATITUDE & LONGITUDE //If we have all the necessary information if (rawLatitude != "" && rawNSindicator != "" && rawLongitude != "" && rawEWindicator != "") { parseCoordinates(rawLongitude, rawLatitude, rawNSindicator, rawEWindicator); } //DATE TIME //If we have the information if (rawUTCtime != "" && rawUTCdate != "") { parseTime(rawUTCtime, rawUTCdate); } // SPEED //If we have the information if (rawSpeedinKnots != "") { // Convert to Kilometres per hour _speedKph = double.Parse(rawSpeedinKnots, NmeaCultureInfo) * KPHPerKnot; } // BEARING/COURSE //If we have the information if (rawCourse != "") { // Indicate that the sentence was recognized _course = double.Parse(rawCourse, NmeaCultureInfo); } // SATELLITE FIX //If we have the information if (rawStatus != "") { switch (rawStatus) { case "A": _hasFix = true; break; case "V": _hasFix = false; break; } } //MAGNETIC VARIATION //if we have the information if (rawMagneticVariationDegrees != "" & rawMagneticVariationEW != "") { _magVar = double.Parse(rawMagneticVariationDegrees, NmeaCultureInfo); if (rawMagneticVariationEW == "W") { _magVar = -_magVar; } } return true; } // Interprets a "Satellites in View" NMEA sentence public bool ParseGPGSV(string sentence) { try { string[] Words = GetWords(sentence); string rawNumberOfMessages = Words[1]; string rawSequenceNumber = Words[2]; string rawSatellitesInView = Words[3]; _satelliteCount = int.Parse(rawSatellitesInView, NmeaCultureInfo); if (rawSequenceNumber == "1") { _satellites.Clear(); _allSatellitesLoaded = false; } if (rawSequenceNumber == rawNumberOfMessages) { _allSatellitesLoaded = true; } int index = 4; while (index <= 16 && Words[index] != "") { Satellite tempSatellite = new Satellite(); string ID = Words[index]; if (ID != "") { int.TryParse(ID, NumberStyles.Integer, NmeaCultureInfo, out tempSatellite.ID); } string Elevation = Words[index + 1]; if (Elevation != "") { tempSatellite.Elevation = double.Parse(Elevation, NmeaCultureInfo); } string Azimuth = Words[index + 2]; if (Azimuth != "") { tempSatellite.Azimuth = System.Convert.ToDouble(Azimuth, CultureInfo.InvariantCulture); } string SNR = Words[index + 3]; if (SNR == "") { tempSatellite.SNR = double.NaN; } else { tempSatellite.SNR = System.Convert.ToDouble(SNR, CultureInfo.InvariantCulture); } index = index + 4; _satellites.Add(tempSatellite); } } catch (Exception) { } // Indicate that the sentence was recognized return true; } // Interprets a "Fixed Satellites and DOP" NMEA sentence public bool ParseGPGSA(string sentence) { string[] Words = GetWords(sentence); string rawMode1a = Words[1]; string rawMode1b = Words[2]; int[] satIDs_ = new int[_maxChannels]; int idx = 3; int satCount = 0; for (int i = 0; i < _maxChannels; i++) { try { satIDs_[i] = System.Convert.ToInt32(Words[idx]); satCount++; } catch (FormatException) { satIDs_[i] = int.MaxValue; } idx++; } _satIDs = new int[satCount]; for (int i = 0; i < satCount; i++) { _satIDs[i] = satIDs_[i]; } string rawPDOP = Words[idx]; string rawHDOP = Words[idx + 1]; string rawVDOP = Words[idx + 2]; if (rawMode1a == "M") { _isForced2D3D = true; } if (rawMode1a == "A") { _isForced2D3D = false; } if (rawMode1b != "") { _fixMode = System.Convert.ToInt32(rawMode1b); } if (rawPDOP != "") _PDOP = System.Convert.ToDouble(rawPDOP, CultureInfo.InvariantCulture); if (rawHDOP != "") _HDOP = System.Convert.ToDouble(rawHDOP, CultureInfo.InvariantCulture); if (rawVDOP != "") _VDOP = System.Convert.ToDouble(rawVDOP, CultureInfo.InvariantCulture); return true; } //Fix Data public bool ParseGPGGA(string sentence) { bool result = false; try { string[] words = GetWords(sentence); if (words.Length >= 15) { string rawUTCtime = words[1]; string rawLatitude = words[2]; string rawNSIndicator = words[3]; string rawLongitude = words[4]; string rawEWIndicator = words[5]; string rawPositionFix = words[6]; string rawSatellitesUsed = words[7]; string rawHDOP = words[8]; string rawAltitude = words[9]; string rawAltitudeUnits = words[10]; string rawGeoidSeperation = words[11]; string rawSeperationUnits = words[12]; string rawDGPSAge = words[13]; string rawDGPSStationID = words[14]; parseTime(rawUTCtime, ""); parseCoordinates(rawLongitude, rawLatitude, rawNSIndicator, rawEWIndicator); if (rawSatellitesUsed != "") { _satellitesUsed = int.Parse(rawSatellitesUsed, NmeaCultureInfo); } if (rawAltitude != "") { _altitude = double.Parse(rawAltitude, NmeaCultureInfo); } if (rawGeoidSeperation != "") { _geoIdSeperation = double.Parse(rawGeoidSeperation, NmeaCultureInfo); } if (rawDGPSAge != "") { _DGPSAge = double.Parse(rawDGPSAge, NmeaCultureInfo); } if (rawDGPSStationID != "") { _DGPSID = int.Parse(rawDGPSStationID, NmeaCultureInfo); } if (rawPositionFix != "") { _positionFix = int.Parse(rawPositionFix, NmeaCultureInfo); } result = true; } } catch (Exception) { result = false; } return result; } /// /// /// /// /// true if the parse was successful public bool ParseGPVTG(string sentence) { string[] Words = GetWords(sentence); string rawCourseTrue = Words[1]; string rawReferenceTrue = Words[2]; string rawCourseMag = Words[3]; string rawReferenceMag = Words[4]; string rawSpeedKnots = Words[5]; string rawSpeedKPH = Words[7]; if (rawSpeedKPH != "") { _speedKph = double.Parse(rawSpeedKPH, NmeaCultureInfo); } return true; } /// /// /// /// /// returns true if the calculated checksum matches the received checksum private bool IsValidSentence(string sentence) { string readChecksum = sentence.Substring(sentence.IndexOf("*") + 1).Trim(); return readChecksum == GetChecksum(sentence); } /// /// Tries to parse coordinates. Sets Latitude and Longitude if possible. /// /// /// /// /// private void parseCoordinates(string rawLongitude, string rawLatitude, string rawNSindicator, string rawEWindicator) { //Latitude try { double latHours = double.Parse(rawLatitude.Substring(0, 2), NmeaCultureInfo); double latMinutes = double.Parse(rawLatitude.Substring(2), NmeaCultureInfo); _latitude = latHours + latMinutes / 60; if (rawNSindicator == "S") { _latitude = -_latitude; } //Longitude double lonHours = double.Parse(rawLongitude.Substring(0, 3), NmeaCultureInfo); double lonMinutes = double.Parse(rawLongitude.Substring(3), NmeaCultureInfo); _longitude = lonHours + lonMinutes / 60; if (rawEWindicator == "W") { _longitude = -_longitude; } } catch (Exception) { } } /// /// Tries to parse the time, sets SatelliteTime and Timestamp if possible /// /// /// private void parseTime(string rawUTCtime, string rawUTCdate) { //two lines of code to save us from y2.1k DateTime TodayTime = DateTime.Now; int y2k = TodayTime.Year / 100; int UtcHours = int.Parse(rawUTCtime.Substring(0, 2), NmeaCultureInfo); int UtcMinutes = int.Parse(rawUTCtime.Substring(2, 2), NmeaCultureInfo); int UtcSeconds = int.Parse(rawUTCtime.Substring(4, 2), NmeaCultureInfo); int UtcMilliseconds = 0; // Extract milliseconds if it is available if (rawUTCtime.Length > 7) { UtcMilliseconds = int.Parse(rawUTCtime.Substring(7), NmeaCultureInfo); } //Read the date from the satellite if (rawUTCdate != "") { int DD = int.Parse(rawUTCdate.Substring(0, 2), NmeaCultureInfo); int MM = int.Parse(rawUTCdate.Substring(2, 2), NmeaCultureInfo); int YY = int.Parse(rawUTCdate.Substring(4, 2), NmeaCultureInfo); _satelliteTime = new DateTime(y2k * 100 + YY, MM, DD, UtcHours, UtcMinutes, UtcSeconds, UtcMilliseconds); } else { _satelliteTime = new DateTime(_satelliteTime.Year, _satelliteTime.Month, _satelliteTime.Day, UtcHours, UtcMinutes, UtcSeconds, UtcMilliseconds); } TimeSpan deltaTime = System.TimeZone.CurrentTimeZone.GetUtcOffset(_satelliteTime); _timestamp = _satelliteTime + deltaTime; } /// /// Calculates the checksum for a sentence /// /// /// public string GetChecksum(string sentence) { int Checksum = 0; foreach (char Character in sentence) { if (Character == '$') { // Ignore the dollar sign } else if (Character == '*') { // Stop processing before the asterisk break; } else { // Is this the first value for the checksum? if (Checksum == 0) { // Yes. Set the checksum to the value Checksum = Convert.ToByte(Character); } else { // No. XOR the checksum with this character's value Checksum = Checksum ^ Convert.ToByte(Character); } } } // Return the checksum formatted as a two-character hexadecimal return Checksum.ToString("X2"); } } }