Library tutorials & articles

Writing GPS Applications in .NET: Part 1

A World-Class Interpreter

International readers may have spotted a subtle problem early on that was not handled in the listings – numbers were being reported in the numeric format used in the United States! Countries like Belgium and Switzerland which use different formats for numbers, require adjustments to the interpreter in order to work at all. Fortunately, the .NET framework includes built-in support for converting numbers between different cultures, so the changes to the interpreter required are straightforward. In the interpreter, the only fractional value is speed, so only one change is necessary. The NmeaCultureInfo variable represents the culture used for numbers within NMEA sentences. The Double.Parse method is then used with this variable to convert speed into the machine’s local culture. Listing 1-8 shows the completed interpreter, now ready for use internationally.

Listing 1-8: The completed interpreter, suitable for use anywhere in the world.

'**  Listing 1-8.  Adding support for international cultures
'*************************************************************
Imports System.Globalization
Public Class NmeaInterpreter
  ' Represents the EN-US culture, used for numers in NMEA sentences
  Private NmeaCultureInfo As New CultureInfo("en-US")
  ' Used to convert knots into miles per hour
  Private 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)
  ' 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"
        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)
      End If
      ' 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 = 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, _
            SignalToNoiseRatio)
        End If
      End If
    Next
    ' 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)
          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

Final Thoughts

You should now have a good understanding that an NMEA interpreter is all about extracting words from sentences. You can harness the power of satellites to determine your location, synchronize your computer clock, find your direction, watch your speed, and point to a satellite in the sky on a cloudy day. This interpreter will also work with the .NET Compact Framework without any modifications. If sentences were also stored in a file, the interpreter can be used to play back an entire road trip. These are all great features, especially considering the small size of the class, but is this interpreter ready to navigate your car and help your golf game? Not quite yet. There is one important topic remaining which is required to make GPS applications safe for the real world: precision.
GPS devices are designed to report any information they find, even if the information is inaccurate. In fact, information about the current location can be off as much as half a football field, even when devices are equipped with the latest DGPS and WAAS correction technologies! Unfortunately, several developers are not aware of this problem. There are some third-party components out there which are not suitable for commercial applications that require enforcing a minimum level of precision. Keep this article handy, however, because in part two of this series, I will explain precision enforcement in detail and take the interpreter even further to make it suitable for professional, high-precision applications!

(To be continued...)

[NOTE: Please indicate your interest in part two by rating this article.]

Comments

  1. 02 Nov 2009 at 09:37

    http://serialcs.sourceforge.net Reads current GPS position using NMEA sentence GGA, shows on a map (scan, screenshot or downloaded from http://www.openstreetmap.org). Sources and documentation avaialable. If someone prefers C rather than .NET, a similar program cb (sources, makefiles included) is available on http://stoyac.privat.t-online.de.

  2. 29 Jun 2008 at 21:50

    Hi , 

    I need the same program as well.Can you find any information.Please contact me.

    tunc@te-mob.com

     

  3. 21 Aug 2007 at 11:19

     

    The checksum routine has been modified slightly to clarify the Select statement.

    The "$" case is used to initialise Checksum and the If statement in the Else case has been removed.  This works because Checksum = value gives the same result as Checksum = 0 Xor value when processing the first byte.

     

        ' 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 = 0

            For Each Character In sentence

                Select Case Character

                    Case "$"c

                        ' Ignore the dollar sign

                        Checksum = 0

                    Case "*"c

                        ' Stop processing before the asterisk

                        Exit For

                    Case Else

                        ' XOR the checksum with this character's value

                        Checksum = Checksum Xor Convert.ToByte(Character)

                End Select

            Next

            ' Return the checksum formatted as a two-character hexadecimal

            Return Checksum.ToString("X2")

        End Function

  4. 21 Aug 2007 at 11:02

    The NMEA protocol specifies that the last parameter is immediately followed by "*" and a 2-cfharacter checksum.  For example, 3.45,"W"*1A is valid as is 200804,,*1A.  In the fist case there is data in the last parameter and in the second case, because the last parameter has been omitted. we effectively have "200804,," & "" & "*" & "1A".  That is the data string upto the second last parameter and it's following comma, the omitted final parameter, the asterisc to denote both end of data and the start of  the checksum, and finally the 2-character checksum.

    Jon's excellent articles give us the start for writing our code but does say that additional error trapping is required.

    My change to the GetWords function is:

        Public Function GetWords(ByVal sentence As String) As String()

            Dim temp As String

            temp = sentence.Substring(0, sentence.IndexOf("*")) & ",*"

            Return temp.Split(","c)

        End Function

  5. 19 Aug 2007 at 08:01
    Try my gps string parser http://www.gpsxml.com/gpsxml/service.asmx?op=GPS2XML

    I need somebody to test my mobile APP. You can download the cab file from http://gps.gpsxml.com/viewtopic.php?t=4

    Please feel free to give any comments, suggestions or ideas

    Thanks
    Imtiyaz Momin
    http://gps.gpsxml.com/
    imtu80@hotmail.com








  6. 16 Aug 2007 at 06:01
    I need somebody to test my mobile APP. You can download the cab file from http://gps.gpsxml.com/viewtopic.php?t=4
    Also, added new feature where I can upload picture from the phone using my application and the picture shows up on the trail where the picture was taken.
    Check it out at http://gps.gpsxml.com/tracker.cfm?userID=1
    Please feel free to give any comments, suggestions or ideas

    Thanks
    Imtiyaz Momin
    http://gps.gpsxml.com/
    imtu80@hotmail.com







  7. 28 Jun 2007 at 10:19

    I am a novice programmer and need to get my hands on some code for phone tracking which will plot a phones movement every (n) minutes then plot the position  on a map.

    Any help at all will be greatly appreciated.

    Can anyone please please help me.

    Thanks
    Nev

  8. 21 Jun 2007 at 02:48
    http://gps.imomin.com/tracker.cfm?userID=1

    Above link shows an example which I created for my brother for his carpc.
    It can covertly run on mobile device and send location information via internet.
    let me know what you think and anything can be done with my project.


    Imtiyaz Momin
    imtu80@hotmail.com
    http://www.imomin.com








  9. 10 Jun 2007 at 15:20

    Regarding with GPRMC message

    $GPRMC,040302.663,A,3939.7,N,10506.6,W,0.27,358.86,200804,,*1A

    Some antennas (like Leadtek) See quotes, transmitt the last parameter before * char like this

    $GPRMC,040302.663,A,3939.7,N,10506.6,W,0.27,358.86,200804,3.45,"W"*1A

    Other antennas (like Garmin), transmit the last parameter like this

    $GPRMC,040302.663,A,3939.7,N,10506.6,W,0.27,358.86,200804,3.45,"W",*1A

    Note that has a colon "," before * char.

    Please someone explain me this question.

    Thanks.

    Jean

  10. 19 Apr 2007 at 07:21
    Hi,

    Can I get the c# code for this nice article?

    Regards,
    Ravi






  11. 12 Apr 2007 at 12:31

    Well...as soon as you write a post you figure soething out.

    It seems that the error is only thrown when there isn't enough data in the nmea sentence because if I use this sentence

    $GPGSV,2,2,08,14,28,047,21,28,20,259,,19,16,156,26,23,07,254,41*71

    it seems to work fine. I think the problem is with the SNR for the first satellite in view as the previous NMEA didn't have a value for this.

    Hope this helps someone.

  12. 12 Apr 2007 at 12:23

    Nice article, very useful except I've come across a problem with the GPGSV sentences. If you try and run this code

    MyInterpreter.Parse("$GPGSV,2,1,08,11,78,132,,20,63,226,,01,46,102,,17,33,308,*71")

    It will throw an error (Conversion from string "*71" to type 'Integer' is not valid.)

    This is because of this line of code

    SignalToNoiseRatio =

    CType(Words(Count * 4 + 3), Integer)

    Does anyone know how to solve this problem as it's kind of a thorn in my side and I can't get rid of it.

    Cheers

    Jon

  13. 14 Mar 2007 at 21:31

    I like to write GPS navigating system pleas tell me what kind of hardware and signals I need, I am living in Sri Lanka, how I get that connection

  14. 23 Mar 2006 at 08:32

    If you require Ordnance Survey Grid References, I have written an article on how to do so using NMEA data derived from Jon Person's excellent NMEAinterpreter class.


    GPS- Deriving British Ordnance Survey Grid Referece from NMEA data

    AlexE


  15. 14 Mar 2006 at 13:08
    I spotted this slight error too and made the same correction as c_programming_guru.
    However this led to a new error as the final word in the string contained the checksum.
    ie in the example sentence

    $GPGSV, 3, 1, 10, 24, 82, 023, 40, 05, 62, 285, 32, 01, 62, 123, 00, 17, 59, 229, 28*70

    the final word would be 28*70
    this caused an error when trying to convert this to an int32 in the line

    SignalToNoiseRatio = Convert.ToInt32(Words[Count * 4 + 3]);

    My solution was to remove the checksum part of the sentence in GetWords, before splitting the sentence
     
        public string[] GetWords(string sentence)
        {
            //remove the final * + checksum
            sentence = sentence.Substring(0, sentence.IndexOf("*"));
            //now split it up
            return sentence.Split(',');
        }


    Assuming that I'm not talking out of my rear, I hope this proves useful
    Alex
     























  16. 13 May 2005 at 18:11
    I love this product. Excellent Job on this product, Jon Person !. I'm really excited infact I am going to go buy and support your product 360%!... I just have to correct 1 minor error. I feel you should be aware or maybe your are already....don't know might have been a typo,... I'm not much of a VB coder but, I believe during the ParseGPGSV() function, which is suppose to parse the "Satellites in View" $GPGSV sentence.... If you look closesly. During the...

    Original Article: http://www.gpsdotnet.com/kb/article.aspx?id=10350
    Example Sentence: $GPGSV, 3, 1, 10, 24, 82, 023, 40, 05, 62, 285, 32, 01, 62, 123, 00, 17, 59, 229, 28*70

    Each Block consist of 4 words.. "24, 82, 023, 40"  ==  "PseudoRandomCode, Elevation, Azimuth, SignalToNoiseRatio"
    According to your Article... SNR values range from 0-50...where 50 means "Excellent Signal"...though SNR can go as high as 99, like you've stated.




    ' Yes. "Extract satellite information and report it" -- section.
    ' ERROR suspect

        Azimuth = CType ( Words ( Count * 4 + 2 ), Integer)
        SignalToNoiseRatio = CType( Words ( Count * 4 + 2 ), Integer)    
                                                                             '// ^--- Logical Bug, I believe it should be ....

    ' CORRECTION
        SignalToNoiseRatio = CType ( Words ( Count * 4 + 3), Integer)
                                                                             '// ^--- This would be correct.


    Otherwise I believe it would return the same value from the Azimuth extraction. So you wouldn't get any SNR information to be able to base precision correctly. For the future of dependability and reliability of code production, I post this correction. As far as the Signal Strength of the satellites, within the Notification, of the Event call to,...

             RaiseEvent SatelliteReceived ( PseudoRandomCode,  Azimuth,  Elevation,  SignalToNoiseRatio )

    I believe it would generate incorrect results...to what ever is going to be done with the SNR variable.
    Please feel free to e-mail me at deciphered_scripturez@yahoo.com for additional details...I really Thank you, Jon Person, for your hard work and time put into this and I really would LOVE to help as much as possible...I believe in your product. !    

                                                                                                                                                                  Thank You,
                                                                                                                                                                   -- c_programming_guru
  17. 02 Apr 2005 at 20:37

    As I understand it, that should be fine... so long as you add a GPS receiver?

  18. 11 Jan 2005 at 13:13
    Hi
    Just curious, what kind of hardware and signal service will be required to make it happen?
    I have a ViewSonic V37 pocket PC. Will that be a good instrument for this? Please advice.
    Thanks
    Pankaj
  19. 08 Oct 2004 at 08:44
    Jon Person's article is a particularly interesting look into how GPS systems work.  I've been interested in GPS for a long time but not knowing how this new "black box" technology worked kept me away from delving into it.  After reading Jon's article I now realize that it's not nearly as difficult as I once thought.  I plan on buying his “GPS.NET Global Positioning SDK” component so I can begin writing applications utilizing GPS.  Who knows, perhaps I'll stumble onto a successful application with vertical market benefit.

    Thank you, Jon.  I look forward to your next article.

    Steve
  20. 01 Jan 1999 at 00:00

    This thread is for discussions of How to Write a GPS Application.

Leave a comment

Sign in or Join us (it's free).

Jon Person 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 com...
AddThis

Related discussion

Related podcasts

  • More jQuery in ASP.NET

    In this episode Chris Brandsma, Rick Strahl, Dave Ward, Bertrand Le Roy, and Scott Koon conclude their discussion of Microsoft's jQuery in ASP.NET announcement1.This episode of the Alt.NET Podcast is brought to you by LLBLGen Pro, the most mature O/R mapper and code generator out there.Are ...

Events coming up

  • Nov 18

    15 Minutes of Fame

    Dresher, United States

    This is a yearly tradition. We select 10 of the favorite speakers from monthly meetings, code camps, and hands on labs. Each one does a 15 minute talk on their favorite .NET technology. This is our 10th anniversary so we plan a gala event with special prizes and refreshments.

We'd love to hear what you think! Submit ideas or give us feedback