Writing Your Own GPS Applications: Part II


Sample Image - WritingGPSApplications2.jpgIntroduction

In part one of this article, I described how to write an interpreter for raw GPS NMEA data. The article included source code in VB.NET which harnessed the power of GPS satellites to determine the current location, synchronize the computer clock to atomic time, and point to a satellite on a cloudy day. The interpreter also works with hand-held devices and supports international developers. Yet, the interpreter was really not suitable for commercial use because it did not monitor precision. Without precision, an application could end up making unintelligent business decisions such as accidentally telling a driver to turn left into an alley, or worse. In this second part, I'll cover precision in detail and talk about what it takes to make GPS applications smart enough for in-car navigation and reliable enough for commercial use.

Causes of Precision Error

There are several phenomenon which can cause poor precision. For example, when satellite radio signals are transmitted, they are distorted by the troposphere and especially the ionosphere. In fact, satellites very low on the horizon are not good for getting a fix because the signals travel through so much of the atmosphere. Some GPS devices may even exclude these satellites from a fix to avoid the precision problems they would cause.


 
Figure 2-1: Satellite 1's radio signal travels through less of the atmosphere, resulting in less distortion. Satellite 2 is low on the horizon, however, resulting in significant atmospheric distortion.

Fortunately, atmospheric distortion can be measured and corrected for the most part. This is achieved by the use of GPS ground stations, fixed locations which constantly measure distortions in satellite radio signals. Calculated corrections are then broadcasted by radio which, when combined with the actual satellite signal, give a GPS receiver the ability to correct distortions in real-time.


 
Figure 2-2: Distortions in radio signals are corrected by combining satellite signals (1 and 2) with correction information transmitted via DGPS ground stations (3 and 4).

Precision errors can be compounded by slight inaccuracies in each satellite's "ephemeris". Ephemeris is a table giving the coordinates of a celestial body over time. If the satellite's actual course deviates from its ephemeris, precision can be further diluted. This sort of error can only be corrected by firing small rockets on the satellites themselves. Adjustments are transmitted from the GPS Master Control Station at Schriever Air Force Base in Colorado Springs, Colorado.


 
Figure 2-3: Deviations in a satellite's actual orbit path can also cause loss in precision.

As I covered in part one of this article, each GPS satellite has four on-board atomic clocks: two cesium atomic clocks and two rubidium atomic clocks which are accurate to 1 second per 300,000 years! Still, even microfractions of a second error in these clocks can cause positional error because distance is measured at the speed of light. The Master Control Station keeps these errors at a minimum by uploading corrective information to satellites twice a day, every day.

The last detriment to GPS precision is an effect called "multipath", which is an effect caused when a receiver receives not only the satellite's signal, but additional signals which bounced off buildings and other obstacles. Deflected signals take a longer path to the receiver and are thus delayed. If they are used by the receiver, the measured distance to a satellite is overestimated, resulting in inaccurate multilateration. More advanced receivers solve multipath problems by utilizing only the first signal detected (which is the most direct path from the satellite), then discarding any delayed signals.


 
Figure 2-4: A receiver is confused by "multipath", where several reflected signals are received (red) along with the direct radio signal (green).

Solving all of these precision problems is done by using more sophisticated GPS receivers which use real-time correction data such as WAAS (for North America) and EGNOS (for Europe). Yet, these problems cause relatively small inaccuracies when compared with Geometric Dilution of Precision, which can cause a receiver to be inaccurate by more than an American football field. Fortunately, Geometric DOP is the easiest to manage with the right programming techniques.

Geometric Dilution of Precision

GPS devices calculate your position using a technique called "3-D multilateration", which is the process of figuring out where several spheres intersect. In the case of GPS, each sphere has a satellite at its center; the radius of the sphere is the calculated distance from the satellite to the GPS device. Ideally, these spheres would intersect at exactly one point, causing there to be only one possible solution to the current location, but in reality, the intersection forms more of an oddly-shaped area. The device could be located within any point in the area, forcing devices to choose from many possibilities. Figure 2-4 shows such an area created from three satellites (using part one's $GPGSV sentence). The current location could be any point within the gray-colored area. Precision is said to be "diluted" when the area grows larger, which leads to this article's focus: dilution of precision. The monitoring and control of dilution of precision (or DOP for short) is the key to writing high-precision applications.


 
Figure 2-5: GPS devices must choose one of several possible solutions to the current location.

DOP values are reported in three types of measurements: horizontal, vertical, and mean. Horizontal DOP (or HDOP) measures DOP as it relates to latitude and longitude. Vertical DOP (or VDOP) measures precision as it relates to altitude. Mean DOP, also known as Position DOP (PDOP), gives an overall rating of precision for latitude, longitude and altitude. Each DOP value is reported as a number between one and fifty where fifty represents very poor precision and one represents ideal accuracy. Table 2-1 lists what I believe to be an accurate breakdown of DOP values.

DOP Rating Description
1 Ideal This is the highest possible confidence level to be used for applications demanding the highest possible precision at all times.
2-3 Excellent At this confidence level, positional measurements are considered accurate enough to meet all but the most sensitive applications.
4-6 Good Represents a level that marks the minimum appropriate for making business decisions. Positional measurements could be used to make reliable in-route navigation suggestions to the user.
7-8 Moderate Positional measurements could be used for calculations, but the fix quality could still be improved. A more open view of the sky is recommended.
9-20 Fair Represents a low confidence level. Positional measurements should be discarded or used only to indicate a very rough estimate of the current location.
21-50 Poor At this level, measurements are inaccurate by as much as half a football field and should be discarded.

Table 2-1: Jon's interpretation of dilution of precision values.

Looking again at figure 2-4, three satellites created a large area of possible solutions. This situation could be improved by two factors: adding more satellites to the fix, and using satellites evenly distributed throughout the sky. What would figure 2-4 look like if the situation was improved like this? Figure 2-5 shows Figure 2-4 after three more evenly-distributed satellites have been added.


 
Figure 2-6: Three more evenly-distributed satellites are added to figure 2-4, creating a high-precision environment where dilution of precision is low.

Determining Precision Needs

Now that the mechanics of precision have been explained, the next step is to figure out how to determine the actual precision needs of an application. As a general rule of thumb, an HDOP value of six or less is recommended for any application which makes suggestions to the user based on the current location. For example, in-car navigation programs which tell the user to "turn left now" should ignore positional measurements when HDOP is greater than six. But is six really good enough? How can developers figure out which HDOP values to use for their own applications? To answer these kinds of questions, I like to use a simple formula:

Accuracy of GPS Device * DOP = Maximum Allowable Error

This formula uses DOP as a factor of error which, when combined with the accuracy of the GPS device being used, yields the maximum error allowed by a level of DOP in the form of a specific, measurable distance. Another general rule of thumb is that typical consumer GPS devices are capable of between 5-7 meters of accuracy without enhancements like DGPS or WAAS, or an average of six meters. Using the in-car navigation HDOP of six and a typical GPS device, the maximum error allowed is 6m * 6 = 36 meters, or 118 feet. Given that a downtown city block is roughly 475 feet square, the maximum allowable error is about a quarter of a city block. This is precise enough to make sure that the driver turns at the correct road. On the other hand, an HDOP of twelve results in an allowable error of half a city block (237 feet), which could cause drivers to turn down an alley accidentally. So, using the formula, it is possible to use an HDOP greater than six for in-car navigation, but not by much.

The trick to using this formula is researching real-world distances, especially the smallest important distances. To demonstrate, take a look at golf. Does golf require more precision than in-car navigation? A golf program needs to tell the user which golf club to use in order to make the best shot. Some research into important golf distances finds that for most players, there is a regular distance interval between clubs of about 10-15 yards. Therefore, a golf program needs no more than 10 yards (9.1 meters) of allowable error to consistently suggest the right club. When 9.1 meters is put into the formula as Maximum Allowable Error, the maximum HDOP comes out to 3. So, golfing applications require about twice the precision as in-car navigation systems.

Why not skip the formulas and always enforce an HDOP of one? This looks like a reasonable practice, but greater precision requires greater satellite visibility. An in-car navigation system will probably not get an HDOP of one (or even three) downtown because signals are being obscured by buildings. If the enforced HDOP is too small, the application will throw out too many positional readings and just sit there while the driver loses patience. Golfing applications, on the other hand, can realistically enforce a small HDOP because they operate outdoors. The golfer's PDA is likely to have plenty of open sky enough to pick up several evenly-distributed satellites, unless their ball is in the middle of the woods, in which case they're on their own.

To summarize, successful GPS software developers will use the formula to determine the greatest possible DOP number. This will ensure that the application minimizes most problems due to inaccuracy while at the same time allowing the application to function in the poorest possible satellite visibility conditions. This practice will maximize the value and versatility of any GPS application.

Enforcing Precision

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 I 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 received

        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(

            float.Parse(Words[1].Substring(6), NmeaCultureInfo) * 1000);

      }

      // 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 + 3]);

          // 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 received

        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(Single.Parse(Words(1).Substring(6), NmeaCultureInfo) * 1000, 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 

High Precision in Action

Enforcing maximum DOP values is the easiest part of the whole programming process because enforcing precision is a matter of ignoring positional measurements above your maximum allowable DOP amount. This can be done in one if statement. To best demonstrate this, I've written a small application (the "Demo" linked at the start of this article) which uses the NMEA interpreter to enforce a maximum DOP of 6.

[C#]

public class HighPrecisionTest
{
 
private NmeaInterpreter MyInterpreter = new NmeaInterpreter();
 
private int MaximumDOPAllowed = 6;
 
private double CurrentHDOP; 

  public HighPrecisionTest()
  {
   
// Bind events for dilution of position
    MyInterpreter.HDOPReceived +=
new System.EventHandler(OnHDOPReceived);
    MyInterpreter.PositionReceived +=
new System.EventHandler(OnPositionReceived);
  } 

  public void Test()
  {
   
// Parse satellite information (HDOP is 50.0)
    MyInterpreter.Parse(
"$GPGSA,A,1,,,,,,,,,,,,,50.0,50.0,50.0*05");
   
// Parse the current position
    MyInterpreter.Parse(
   
"$GPRMC,225233.990,V,3939.4000,N,10506.4000,W,0.00,51.40,280804,,*35");
   
// Parse satellite information (HDOP is 1.2)
    MyInterpreter.Parse(
   
"$GPGSA,A,3,11,29,07,08,19,28,26,,,,,,2.3,1.2,2.0*30");
   
// Parse the current position again
    MyInterpreter.Parse(
   
"$GPRMC,012558.584,A,3939.7000,N,10506.7000,W,0.00,198.07,290804,,*11");
  } 

  private void OnHDOPReceived(double value)
  {
   
// Remember the current HDOP value
    CurrentHDOP = value;
  } 

  private void OnPositionReceived(string latitude, string longitude)
  {
   
// Is the HDOP at least six?
   
if (CurrentHDOP <= MaximumDOPAllowed)
    {
     
// Yes.  Display the current position
      Debug.WriteLine(
"You are here: " + latitude + ", " + longitude);
    }
   
else
    {
     
// No.  Discard this positional measurement
      Debug.WriteLine(
"The received location is not precise enough to use.");
    }
  }

}

[VB.NET]

Public Class HighPrecisionTest
 
  Private WithEvents MyInterpreter As New NmeaInterpreter
  Private MaximumDOPAllowed As Integer = 6
  Private CurrentHDOP As Double
 
  Public Sub Test()
    ' Parse satellite information (HDOP is 50.0)
    MyInterpreter.Parse("$GPGSA,A,1,,,,,,,,,,,,,50.0,50.0,50.0*05")
    ' Parse the current position
    MyInterpreter.Parse("$GPRMC,225233.990,V,3939.4000,N," & _
                        "10506.4000,W,0.00,51.40,280804,,*35")
    ' Parse satellite information (HDOP is 1.2)
    MyInterpreter.Parse("$GPGSA,A,3,11,29,07,08,19,28,26,,,,,,2.3,1.2,2.0*30")
    ' Parse the current position again
    MyInterpreter.Parse("$GPRMC,012558.584,A,3939.7000,N," & _
                        "10506.7000,W,0.00,198.07,290804,,*11")
  End Sub
 
  Private Sub OnHDOPReceived(ByVal value As Double) _
          Handles MyInterpreter.HDOPReceived
    ' Remember the current HDOP value
    CurrentHDOP = value
  End Sub
 
  Private Sub OnPositionReceived(ByVal latitude As String, _
    ByVal longitude As String) Handles MyInterpreter.PositionReceived
    ' Is the HDOP at least six?
    If CurrentHDOP <= MaximumDOPAllowed Then
      ' Yes.  Display the current position
      Debug.WriteLine("You are here: " & latitude & ", " & longitude)
    Else
      ' No.  Discard this positional measurement
      Debug.WriteLine("The received location is not precise enough to use.")
    End If
  End Sub
End Class

And that's it! Armed with a deep understanding of GPS precision and how to keep it tightly controlled, you can now develop location-based services suitable for the real world.

Conclusion

There are several ways to distort a GPS satellite signal. Some are corrected by the Department of Defence and others can be corrected in your GPS receiver using real-time ground station correction signals. The only precision problem which is left to you to control is Geometric Dilution of Precision. Controlling GDOP is the key to writing commercial-grade GPS applications. A small mathematical formula can be applied to determine the maximum allowable DOP for a particular application. The maximum allowable error should be the greatest possible value which minimizes accuracy problems while maximizing operational conditions.

Another factor which helps developers is time itself. Advances in GPS receiver technology are pushing precision to new levels. While precision can be questionable with any consumer GPS device, there will soon be a time when precision to a centimeter is possible. I believe that this level of precision will cause a revolution in industry and pave the way for some truly amazing things: automated construction machines, tracking for every shipping container in the world, traffic control systems that actively prevent traffic jams... and self-guiding golf balls.

The next part of this series deals with mapping, getting GPS data and other geographic information to display in your .NET applications.

Up Next
    Ebook Download
    View all
    Learn
    View all