Writing GPS Applications in .NET: Part 1

Real-Time Satellite Tracking

Knowing the location of satellites is important when determining how precise readings are and how stable a GPS fix is. Since GPS precision will be covered in detail in part two of this series, so this section will focus on interpreting satellite location and signal strength.

There are twenty-four operational satellites in orbit. Satellites are spaced in orbit so that at any time a minimum of six satellites will be in view to users anywhere in the world. Satellites are constantly in motion, which is good because it prevents the existence of “blind spots” in the world with little or no satellite visibility. Just like finding stars in the sky, satellite locations are described as the combination of an azimuth and an elevation. As mentioned above, azimuth measures a direction around the horizon. Elevation measures a degree value up from the horizon between 0° and 90°, where 0° represents the horizon and 90° represents “zenith,” directly overhead. So, if the device says a satellite’s azimuth is 45° and its elevation is 45°, the satellite is located halfway up from the horizon towards the northeast. In addition to location, devices report each satellite’s “Pseudo-Random Code” (or PRC) which is a number used to uniquely identify one satellite from another.

Here’s an example of a $GPGSV sentence:


Each sentence contains up to four blocks of satellite information, comprised of four words. For example, the first block is “24,82,023,40” and the second block is “05,62,285,32” and so on. The first word of each block gives the satellite’s PRC. The second word gives each satellite’s elevation, followed by azimuth and signal strength. If this satellite information were to be shown graphically, it would look like figure 1-1.

(Figure 1-1: Graphical representation of a $GPGSV sentence, where the center of the circle marks the current position and the edge of the circle marks the horizon.)

Perhaps the most important number in this sentence is the “signal-to-noise ratio” (or SNR for short). This number indicates how strongly a satellite’s radio signal is being received. Remember, satellites transmit signals at the same strength, but things like trees and walls can obscure a signal beyond recognition. Typical SNR values are between zero and fifty, where fifty means an excellent signal. (SNR can be as high as ninety-nine, but I’ve never seen readings above fifty even in wide open sky.) In Figure 1-1, the green satellites indicate a strong signal, whereas the yellow satellite signifies a moderate signal (in part two, I will provide a way to classify signal strengths). Satellite #1’s signal is completely obscured. Listing 1-7 shows the interpreter after it is expanded to read satellite info.

Listing 1-7: The interpreter is improved to interpret the location of GPS satellites currently in view.

'**  Listing 1-7.  Extracting satellite information
Public Class NmeaInterpreter
  ' 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)
  ' 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" message was found
        Return ParseGPGSV(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
      Dim Latitude As String = Words(3).Substring(0, 2) & "°"  ' Append hours
      Latitude = Latitude & Words(3).Substring(2) & """"    ' Append minutes
      Latitude = Latitude & Words(4)    ' Append the hemisphere
      Dim Longitude As String = Words(5).Substring(0, 3) & "°" ' Append hours
      Longitude = Longitude & Words(5).Substring(3) & """"    ' Append minutes
      Longitude = Longitude & Words(6)    ' Append the hemisphere
      ' 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.  Convert it into MPH
      Dim Speed As Double = CType(Words(7), Double) * 1.150779
      ' If we're over 55MPH then trigger a speed alarm!
      If Speed > 55 Then RaiseEvent SpeedLimitReached()
      ' Notify of the new speed
      RaiseEvent SpeedReceived(Speed)
    End If
    ' Do we have enough information to extract bearing?
    If Words(8) <> "" Then
      ' Indicate that the sentence was recognized
      Dim Bearing As Double = CType(Words(8), Double)
      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, _
        End If
      End If
    ' Indicate that the sentence was recognized
    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)
            ' No. XOR the checksum with this character's value
            Checksum = Checksum Xor Convert.ToByte(Character)
          End If
      End Select
    ' Return the checksum formatted as a two-character hexadecimal
    Return Checksum.ToString("X2")
  End Function
End Class

You might also like...


About the author

Jon Person

Jon Person United States

Jon Person is the author of the award-winning “GPS.NET” software now in use in over two thousand GPS applications in all industries, from geocaching to disease outbreak prevention. Jon runs his...

Interested in writing for us? Find out more.


Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“Measuring programming progress by lines of code is like measuring aircraft building progress by weight.” - Bill Gates