Now that the precision needs of a GPS application can be determined, it's time to find out what source code is necessary to extract and enforce maximum DOP values. All DOP measurements are packaged into the $GPGSA sentence every few seconds. Here is a sample of a $GPGSA sentence:
$GPGSA,A,3,11,29,07,08,5,17,24,,,,,,2.3,1.2,2.0*30
A skillful GPS application developer could know if positional readings are precise enough to use just by looking at one $GPGSA sentence. Again, the best DOP ratings occur when there are several satellites involved in a fix and the satellites are evenly distributed throughout the sky and at separate elevations -– hitting the GPS device from all angles, so to speak.
The last three words of this sentence are 2.3, 1.2, and 2.0, representing mean, horizontal and vertical DOP, respectively. This sentence represents a high-precision environment (suitable enough for both golfing and driving). Using the final listing from part one of this article (Listing 1-8), a method called “ ParseGPGSA ” is added which extracts DOP values and reports them via three events: “ HDOPReceived ”, “ VDOPReceived ” and “ PDOPReceived ”.
C#
//*********************************************************************
//** A high-precision NMEA interpreter
//** Written by Jon Person, author of "GPS.NET" (www.gpsdotnet.com)
//*********************************************************************
using System;
using System.Globalization;
public class NmeaInterpreter
{
// Represents the EN-US culture, used for numers in NMEA sentences
public static CultureInfo NmeaCultureInfo = new CultureInfo("en-US");
// Used to convert knots into miles per hour
public static double MPHPerKnot = double.Parse("1.150779",
NmeaCultureInfo);
#region Delegates
public delegate void PositionReceivedEventHandler(string latitude,
string longitude);
public delegate void DateTimeChangedEventHandler(System.DateTime dateTime);
public delegate void BearingReceivedEventHandler(double bearing);
public delegate void SpeedReceivedEventHandler(double speed);
public delegate void SpeedLimitReachedEventHandler();
public delegate void FixObtainedEventHandler();
public delegate void FixLostEventHandler();
public delegate void SatelliteReceivedEventHandler(
int pseudoRandomCode, int azimuth, int elevation,
int signalToNoiseRatio);
public delegate void HDOPReceivedEventHandler(double value);
public delegate void VDOPReceivedEventHandler(double value);
public delegate void PDOPReceivedEventHandler(double value);
#endregion
#region Events
public event PositionReceivedEventHandler PositionReceived;
public event DateTimeChangedEventHandler DateTimeChanged;
public event BearingReceivedEventHandler BearingReceived;
public event SpeedReceivedEventHandler SpeedReceived;
public event SpeedLimitReachedEventHandler SpeedLimitReached;
public event FixObtainedEventHandler FixObtained;
public event FixLostEventHandler FixLost;
public event SatelliteReceivedEventHandler SatelliteReceived;
public event HDOPReceivedEventHandler HDOPReceived;
public event VDOPReceivedEventHandler VDOPReceived;
public event PDOPReceivedEventHandler PDOPReceived;
#endregion
// Processes information from the GPS receiver
public bool Parse(string sentence)
{
// Discard the sentence if its checksum does not match our
// calculated checksum
if (!IsValid(sentence)) return false;
// Look at the first word to decide where to go next
switch (GetWords(sentence)[0])
{
case "$GPRMC":
// A "Recommended Minimum" sentence was found!
return ParseGPRMC(sentence);
case "$GPGSV":
// A "Satellites in View" sentence was recieved
return ParseGPGSV(sentence);
case "$GPGSA":
return ParseGPGSA(sentence);
default:
// Indicate that the sentence was not recognized
return false;
}
}
// Divides a sentence into individual words
public string[] GetWords(string sentence)
{
return sentence.Split(',');
}
// Interprets a $GPRMC message
public bool ParseGPRMC(string sentence)
{
// Divide the sentence into words
string[] Words = GetWords(sentence);
// Do we have enough values to describe our location?
if (Words[3] != "" & Words[4] != "" &
Words[5] != "" & Words[6] != "")
{
// Yes. Extract latitude and longitude
// Append hours
string Latitude = Words[3].Substring(0, 2) + "°";
// Append minutes
Latitude = Latitude + Words[3].Substring(2) + "\"";
// Append hours
Latitude = Latitude + Words[4]; // Append the hemisphere
string Longitude = Words[5].Substring(0, 3) + "°";
// Append minutes
Longitude = Longitude + Words[5].Substring(3) + "\"";
// Append the hemisphere
Longitude = Longitude + Words[6];
// Notify the calling application of the change
if(PositionReceived != null)
PositionReceived(Latitude, Longitude);
}
// Do we have enough values to parse satellite-derived time?
if (Words[1] != "")
{
// Yes. Extract hours, minutes, seconds and milliseconds
int UtcHours = Convert.ToInt32(Words[1].Substring(0, 2));
int UtcMinutes = Convert.ToInt32(Words[1].Substring(2, 2));
int UtcSeconds = Convert.ToInt32(Words[1].Substring(4, 2));
int UtcMilliseconds = 0;
// Extract milliseconds if it is available
if (Words[1].Length > 7)
{
UtcMilliseconds = Convert.ToInt32(Words[1].Substring(7));
}
// Now build a DateTime object with all values
System.DateTime Today = System.DateTime.Now.ToUniversalTime();
System.DateTime SatelliteTime = new System.DateTime(Today.Year,
Today.Month, Today.Day, UtcHours, UtcMinutes, UtcSeconds,
UtcMilliseconds);
// Notify of the new time, adjusted to the local time zone
if(DateTimeChanged != null)
DateTimeChanged(SatelliteTime.ToLocalTime());
}
// Do we have enough information to extract the current speed?
if (Words[7] != "")
{
// Yes. Parse the speed and convert it to MPH
double Speed = double.Parse(Words[7], NmeaCultureInfo) *
MPHPerKnot;
// Notify of the new speed
if(SpeedReceived != null)
SpeedReceived(Speed);
// Are we over the highway speed limit?
if (Speed > 55)
if(SpeedLimitReached != null)
SpeedLimitReached();
}
// Do we have enough information to extract bearing?
if (Words[8] != "")
{
// Indicate that the sentence was recognized
double Bearing = double.Parse(Words[8], NmeaCultureInfo);
if(BearingReceived != null)
BearingReceived(Bearing);
}
// Does the device currently have a satellite fix?
if (Words[2] != "")
{
switch (Words[2])
{
case "A":
if(FixObtained != null)
FixObtained();
break;
case "V":
if(FixLost != null)
FixLost();
break;
}
}
// Indicate that the sentence was recognized
return true;
}
// Interprets a "Satellites in View" NMEA sentence
public bool ParseGPGSV(string sentence)
{
int PseudoRandomCode = 0;
int Azimuth = 0;
int Elevation = 0;
int SignalToNoiseRatio = 0;
// Divide the sentence into words
string[] Words = GetWords(sentence);
// Each sentence contains four blocks of satellite information.
// Read each block and report each satellite's information
int Count = 0;
for (Count = 1; Count <= 4; Count++)
{
// Does the sentence have enough words to analyze?
if ((Words.Length - 1) >= (Count * 4 + 3))
{
// Yes. Proceed with analyzing the block.
// Does it contain any information?
if (Words[Count * 4] != "" & Words[Count * 4 + 1] != ""
& Words[Count * 4 + 2] != "" & Words[Count * 4 + 3] != "")
{
// Yes. Extract satellite information and report it
PseudoRandomCode = System.Convert.ToInt32(Words[Count * 4]);
Elevation = Convert.ToInt32(Words[Count * 4 + 1]);
Azimuth = Convert.ToInt32(Words[Count * 4 + 2]);
SignalToNoiseRatio = Convert.ToInt32(Words[Count * 4 + 2]);
// Notify of this satellite's information
if(SatelliteReceived != null)
SatelliteReceived(PseudoRandomCode, Azimuth,
Elevation, SignalToNoiseRatio);
}
}
}
// Indicate that the sentence was recognized
return true;
}
// Interprets a "Fixed Satellites and DOP" NMEA sentence
public bool ParseGPGSA(string sentence)
{
// Divide the sentence into words
string[] Words = GetWords(sentence);
// Update the DOP values
if (Words[15] != "")
{
if(PDOPReceived != null)
PDOPReceived(double.Parse(Words[15], NmeaCultureInfo));
}
if (Words[16] != "")
{
if(HDOPReceived != null)
HDOPReceived(double.Parse(Words[16], NmeaCultureInfo));
}
if (Words[17] != "")
{
if(VDOPReceived != null)
VDOPReceived(double.Parse(Words[17], NmeaCultureInfo));
}
return true;
}
// Returns True if a sentence's checksum matches the
// calculated checksum
public bool IsValid(string sentence)
{
// Compare the characters after the asterisk to the calculation
return sentence.Substring(sentence.IndexOf("*") + 1) ==
GetChecksum(sentence);
}
// Calculates the checksum for a sentence
public string GetChecksum(string sentence)
{
// Loop through all chars to get a checksum
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");
}
}
VB.NET
' *********************************************************************
' ** A high-precision NMEA interpreter
' ** Written by Jon Person, author of "GPS.NET" (www.gpsdotnet.com)
' *********************************************************************
Imports System
Imports System.Globalization
Public Class NmeaInterpreter
' Represents the EN-US culture, used for numers in NMEA sentences
Public Shared NmeaCultureInfo As New CultureInfo("en-US")
' Used to convert knots into miles per hour
Public Shared MPHPerKnot As Double = Double.Parse("1.150779", _
NmeaCultureInfo)
' Raised when the current location has changed
Public Event PositionReceived(ByVal latitude As String, _
ByVal longitude As String)
Public Event DateTimeChanged(ByVal dateTime As DateTime)
Public Event BearingReceived(ByVal bearing As Double)
Public Event SpeedReceived(ByVal speed As Double)
Public Event SpeedLimitReached()
Public Event FixObtained()
Public Event FixLost()
Public Event SatelliteReceived(ByVal pseudoRandomCode As Integer, _
ByVal azimuth As Integer, _
ByVal elevation As Integer, _
ByVal signalToNoiseRatio As Integer)
Public Event HDOPReceived(ByVal value As Double)
Public Event VDOPReceived(ByVal value As Double)
Public Event PDOPReceived(ByVal value As Double)
' Processes information from the GPS receiver
Public Function Parse(ByVal sentence As String) As Boolean
' Discard the sentence if its checksum does not match our
' calculated checksum
If Not IsValid(sentence) Then Return False
' Look at the first word to decide where to go next
Select Case GetWords(sentence)(0)
Case "$GPRMC"
' A "Recommended Minimum" sentence was found!
Return ParseGPRMC(sentence)
Case "$GPGSV"
' A "Satellites in View" sentence was recieved
Return ParseGPGSV(sentence)
Case "$GPGSA"
Return ParseGPGSA(sentence)
Case Else
' Indicate that the sentence was not recognized
Return False
End Select
End Function
' Divides a sentence into individual words
Public Function GetWords(ByVal sentence As String) As String()
Return sentence.Split(","c)
End Function
' Interprets a $GPRMC message
Public Function ParseGPRMC(ByVal sentence As String) As Boolean
' Divide the sentence into words
Dim Words() As String = GetWords(sentence)
' Do we have enough values to describe our location?
If Words(3) <> "" And Words(4) <> "" _
And Words(5) <> "" And Words(6) <> "" Then
' Yes. Extract latitude and longitude
' Append hours
Dim Latitude As String = Words(3).Substring(0, 2) & "°"
' Append minutes
Latitude = Latitude & Words(3).Substring(2) & """"
' Append the hemisphere
Latitude = Latitude & Words(4)
' Append hours
Dim Longitude As String = Words(5).Substring(0, 3) & "°"
' Append minutes
Longitude = Longitude & Words(5).Substring(3) & """"
' Append the hemisphere
Longitude = Longitude & Words(6)
' Notify the calling application of the change
RaiseEvent PositionReceived(Latitude, Longitude)
End If
' Do we have enough values to parse satellite-derived time?
If Words(1) <> "" Then
' Yes. Extract hours, minutes, seconds and milliseconds
Dim UtcHours As Integer = CType(Words(1).Substring(0, 2), Integer)
Dim UtcMinutes As Integer = CType(Words(1).Substring(2, 2), Integer)
Dim UtcSeconds As Integer = CType(Words(1).Substring(4, 2), Integer)
Dim UtcMilliseconds As Integer
' Extract milliseconds if it is available
If Words(1).Length > 7 Then UtcMilliseconds = _
CType(Words(1).Substring(7), Integer)
' Now build a DateTime object with all values
Dim Today As DateTime = System.DateTime.Now.ToUniversalTime
Dim SatelliteTime As New System.DateTime(Today.Year, Today.Month,
Today.Day, UtcHours, UtcMinutes, UtcSeconds,
UtcMilliseconds)
' Notify of the new time, adjusted to the local time zone
RaiseEvent DateTimeChanged(SatelliteTime.ToLocalTime)
End If
' Do we have enough information to extract the current speed?
If Words(7) <> "" Then
' Yes. Parse the speed and convert it to MPH
Dim Speed As Double = Double.Parse(Words(7), NmeaCultureInfo)
* MPHPerKnot
' Notify of the new speed
RaiseEvent SpeedReceived(Speed)
' Are we over the highway speed limit?
If Speed > 55 Then RaiseEvent SpeedLimitReached()
End If
' Do we have enough information to extract bearing?
If Words(8) <> "" Then
' Indicate that the sentence was recognized
Dim Bearing As Double = Double.Parse(Words(8), NmeaCultureInfo)
RaiseEvent BearingReceived(Bearing)
End If
' Does the device currently have a satellite fix?
If Words(2) <> "" Then
Select Case Words(2)
Case "A"
RaiseEvent FixObtained()
Case "V"
RaiseEvent FixLost()
End Select
End If
' Indicate that the sentence was recognized
Return True
End Function
' Interprets a "Satellites in View" NMEA sentence
Public Function ParseGPGSV(ByVal sentence As String) As Boolean
Dim PseudoRandomCode As Integer
Dim Azimuth As Integer
Dim Elevation As Integer
Dim SignalToNoiseRatio As Integer
' Divide the sentence into words
Dim Words() As String = GetWords(sentence)
' Each sentence contains four blocks of satellite information.
' Read each block and report each satellite's information
Dim Count As Integer
For Count = 1 To 4
' Does the sentence have enough words to analyze?
If (Words.Length - 1) >= (Count * 4 + 3) Then
' Yes. Proceed with analyzing the block.
' Does it contain any information?
If Words(Count * 4) <> "" And Words(Count * 4 + 1) <> "" _
And Words(Count * 4 + 2) <> "" And Words(Count * 4 + 3) <> "" Then
' Yes. Extract satellite information and report it
PseudoRandomCode = CType(Words(Count * 4), Integer)
Elevation = CType(Words(Count * 4 + 1), Integer)
Azimuth = CType(Words(Count * 4 + 2), Integer)
SignalToNoiseRatio = CType(Words(Count * 4 + 2), Integer)
' Notify of this satellite's information
RaiseEvent SatelliteReceived(PseudoRandomCode, Azimuth, _
Elevation, SignalToNoiseRatio)
End If
End If
Next
' Indicate that the sentence was recognized
Return True
End Function
' Interprets a "Fixed Satellites and DOP" NMEA sentence
Public Function ParseGPGSA(ByVal sentence As String) As Boolean
' Divide the sentence into words
Dim Words() As String = GetWords(sentence)
' Update the DOP values
If Words(15) <> "" Then
RaiseEvent PDOPReceived(Double.Parse(Words(15), NmeaCultureInfo))
End If
If Words(16) <> "" Then
RaiseEvent HDOPReceived(Double.Parse(Words(16), NmeaCultureInfo))
End If
If Words(17) <> "" Then
RaiseEvent VDOPReceived(Double.Parse(Words(17), NmeaCultureInfo))
End If
Return True
End Function
' Returns True if a sentence's checksum matches the calculated checksum
Public Function IsValid(ByVal sentence As String) As Boolean
' Compare the characters after the asterisk to the calculation
Return sentence.Substring(sentence.IndexOf("*") + 1) = _
GetChecksum(sentence)
End Function
' Calculates the checksum for a sentence
Public Function GetChecksum(ByVal sentence As String) As String
' Loop through all chars to get a checksum
Dim Character As Char
Dim Checksum As Integer
For Each Character In sentence
Select Case Character
Case "$"c
' Ignore the dollar sign
Case "*"c
' Stop processing before the asterisk
Exit For
Case Else
' Is this the first value for the checksum?
If Checksum = 0 Then
' Yes. Set the checksum to the value
Checksum = Convert.ToByte(Character)
Else
' No. XOR the checksum with this character's value
Checksum = Checksum Xor Convert.ToByte(Character)
End If
End Select
Next
' Return the checksum formatted as a two-character hexadecimal
Return Checksum.ToString("X2")
End Function
End Class
Comments