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