Home | Work | Play | Photos | Contact | About
18/7/2002
I've just been browsing through my \Dev folder, and rediscovered the Pocket GPS app I wrote as a side project in 2002. I was even more surprised when I loaded it into Visual Studio 2008, compiled it, and it actually still works!
Compass rose | Satellites | Routes | ||
Edit route | Waypoints | Edit waypoint |
This project was interesting. First, I used to have a thing for GPS back then, and I had a lot of fun creating an RS232 library for the .NET Compact Framework. It ended up being published in the Microsoft .NET Compact Framework (Core Reference). The app itself is pretty simple -
The second reason that this remains a memorable project is the kick I got out of parsing and interpreting NMEA 0183 sentences (superseded in the meantime by NMEA 2000), and being able to produce the satellite location and signal strength indicators you see in the second image above.
I've pasted the code to parse NMEA 0183 GGA, GSV and RMC sentences in below, in case it's useful to anyone. Admittedly the code is a bit rubbish, and not how I'd do it today. Sloppy code aside, the glaring issue is that I don't localise UTC time. I didn't include the RS232 code, because these days there are better solutions to that problem than mine. This MSDN page is a great place to start.
Anyway, click the little +/- signs to expand and collapse code blocks, and contact me if you have questions.
#Region "ParseNMEA0183" |
|
Private Sub ParseNMEA(ByVal nmeaSentence As String) Dim _CRPosition As Integer Dim _CurrentSentence As String If nmeaSentence.Length = 0 Then Exit Sub Try _CRPosition = nmeaSentence.IndexOf(vbCr, 0) Do While _CRPosition > 0 _CurrentSentence = nmeaSentence.Substring(0, _CRPosition - 1) If _CRPosition < nmeaSentence.Length - 1 Then nmeaSentence = nmeaSentence.Substring(_CRPosition + 2) _CRPosition = nmeaSentence.IndexOf(vbCr) Else _CRPosition = -1 End If If _CurrentSentence.IndexOf("$GPGGA") >= 0 Then ParseGGA(_CurrentSentence) If _CurrentSentence.IndexOf("$GPGSV") >= 0 Then ParseGSV(_CurrentSentence) If _CurrentSentence.IndexOf("$GPRMC") >= 0 Then ParseRMC(_CurrentSentence) Loop Catch ex As Exception ' Ignore parse errors. End Try End Sub #End Region |
|
#Region "ParseGGA" |
|
' NMEA 0183 GGA sentences contain global positioning system fix data. ' ' Seq. Field Description ' 1 Sentence Id Talker ID and sentence ID: GP = GPS, GGA = fix data ' 2 UTC time UTC time at which fix was taken. Format: hhmmss.sss ' 3 Latitude Latitude ddmm.mmmm ' 4 Latitude hemisphere Latitude hempisphere: N = North, S = South ' 5 Longitude Longitude: dddmm.mmmm ' 6 Longitude hempishere Longitude hemisphere: E = East, W = West ' 7 Position fix Fix quality: 0 = Invalid, 1 = Valid SPS, 2 = Valid DGPS, 3 = Valid PPS ' 8 Satellites used Number of satellites being tracked (0-12) ' 9 HDOP Horizontal dilution of precision ' 10 Altitude Altitude above mean sea level according to WGS-84 ellipsoid ' 11 Altitude units M = Meters ' 12 Geoid separation Geoid separation above mean sea level according to WGS-84 ellipsoid ' 13 Geoid separation units M = Meters ' 14 DGPS age Age of DGPS data in seconds (empty field) ' 15 DGPS station Id DGPS station ID number (empty field) ' 16 Checksum Checksums are optional for most sentences ' ' Samples: ' Signal not acquired: ' $GPGGA,235947.000,0000.0000,N,00000.0000,E,0,00,0.0,0.0,M,,,,0000*00 ' Signal acquired: ' $GPGGA,092204.999,4250.5589,S,14718.5084,E,1,04,24.4,19.7,M,,,,0000*1F Private Sub ParseGGA(ByVal ggaSentence As String) Dim _Position As Integer = -1 Dim _OldPosition As Integer = 0 Dim _UnparsedSentence As String = ggaSentence Dim _CurrentSentence As String = String.Empty Dim _Item As String = String.Empty Dim _Delimiter As String = String.Empty Try For i As Integer = 1 To 15 If i = 15 Then _Delimiter = NMEA_CHECKSUMSTARTCHAR Else _Delimiter = NMEA_VALUEDELIMITER End If _Position = _UnparsedSentence.IndexOf(_Delimiter, _OldPosition + 1) _CurrentSentence = _UnparsedSentence.Substring(_OldPosition + 1, _Position - _OldPosition - 1) If _CurrentSentence.Length > 0 Then Select Case i Case 2 _Item = "UTC Time" Time_Label.Text = TimeSerial( _ Convert.ToInt32(_CurrentSentence.Substring(0, 2)), _ Convert.ToInt32(_CurrentSentence.Substring(2, 2)), _ Convert.ToInt32(_CurrentSentence.Substring(4, 2))).ToLongTimeString Case 3 _Item = "Latitude" Latitude_Label.Text = FormatNumber(ToDegrees(_CurrentSentence), 6) Case 4 _Item = "Latitude Hemisphere" Latitude_Label.Text += " " & _CurrentSentence Case 5 _Item = "Longitude" Longitude_Label.Text = FormatNumber(ToDegrees(_CurrentSentence), 6) Case 6 _Item = "Longitude Hempsphere" Longitude_Label.Text += " " & _CurrentSentence Case 10 _Item = "Altitude" Altitude_Label.Text = FormatNumber(Convert.ToSingle(_CurrentSentence), 2) End Select End If _OldPosition = _Position Next Catch ex As Exception Throw New Exception("Error processing GGA. " & _Item & ": " & _CurrentSentence & vbCrLf & ex.Message) End Try End Sub #End Region |
|
#Region "ParseGSV" |
|
' NMEA 0183 GSV sentences contain data about satellites currently in view. ' ' Seq. Field Description ' 1 Sentence Id Talker ID: GP = GPS, Sentence ID: GSV = satellites in view ' 2 Number of messages Number of messages in complete message (1 to 3) ' 3 Sequence number Sequence number of this message (1 to 3) ' 4 Satellites in view Total number of satellites currently in view ' 5 Satellite Id 1 Range is 1 to 32 ' 6 Elevation 1 Satellite elevation in degrees (0 to 90) ' 7 Azimuth 1 Azimuth in degrees (0 to 359) ' 8 SNR 1 Signal to noise ratio in dBHZ (0 to 99) ' 9 Satellite Id 2 Range is 1 to 32 ' 10 Elevation 2 Satellite elevation in degrees (0 to 90) ' 11 Azimuth 2 Azimuth in degrees (0 to 359) ' 12 SNR 2 Signal to noise ratio in dBHZ (0 to 99) ' 13 Satellite Id 3 Range is 1 to 32 ' 14 Elevation 3 Satellite elevation in degrees (0 to 90) ' 15 Azimuth 3 Azimuth in degrees (0 to 359) ' 16 SNR 3 Signal to noise ratio in dBHZ (0 to 99) ' 17 Satellite Id 4 Range is 1 to 32 ' 18 Elevation 4 Satellite elevation in degrees (0 to 90) ' 19 Azimuth 4 Azimuth in degrees (0 to 359) ' 20 SNR 4 Signal to noise ratio in dBHZ (0 to 99) ' 21 Checksum Checksums are optional for most sentences ' ' Samples: ' Signal not acquired: ' $GPGGA,235947.000,0000.0000,N,00000.0000,E,0,00,0.0,0.0,M,,,,0000*00 ' Signal acquired: ' $GPGGA,092204.999,4250.5589,S,14718.5084,E,1,04,24.4,19.7,M,,,,0000*1F Public Sub ParseGSV(ByVal gsvSentence As String) Dim _Position As Integer = -1 Dim _OldPosition As Integer = 0 Dim _UnparsedSentence As String = gsvSentence Dim _CurrentSentence As String = "" Dim _EndSent As Boolean = False Dim _Item As String = String.Empty Dim _Satellite As Satellite = Nothing Dim _SatsInView As Integer = 0 Try ' GSV token _Item = "GSV Token" _Position = _UnparsedSentence.IndexOf(NMEA_VALUEDELIMITER, _OldPosition + 1) _CurrentSentence = _UnparsedSentence.Substring(_OldPosition + 1, _Position - _OldPosition - 1) _OldPosition = _Position ' total GSV messages in block _Item = "Total GSV Messages" _Position = _UnparsedSentence.IndexOf(NMEA_VALUEDELIMITER, _OldPosition + 1) _OldPosition = _Position ' current GSV message number _Item = "Current GSV Message" _Position = _UnparsedSentence.IndexOf(NMEA_VALUEDELIMITER, _OldPosition + 1) _OldPosition = _Position ' Sats in view _Item = "Satellites in View" _Position = _UnparsedSentence.IndexOf(NMEA_VALUEDELIMITER, _OldPosition + 1) _CurrentSentence = _UnparsedSentence.Substring(_OldPosition + 1, _Position - _OldPosition - 1) _SatsInView = Convert.ToInt32(_CurrentSentence) _OldPosition = _Position If _SatsInView > 0 Then ' Up to 4 sats For i As Integer = 1 To 4 _Satellite = New Satellite _Item = "Satellite ID" _Position = _UnparsedSentence.IndexOf(NMEA_VALUEDELIMITER, _OldPosition + 1) _CurrentSentence = _UnparsedSentence.Substring(_OldPosition + 1, _Position - _OldPosition - 1) _Satellite.ID = Convert.ToInt32(_CurrentSentence) _OldPosition = _Position _Item = "Elevation" _Position = _UnparsedSentence.IndexOf(NMEA_VALUEDELIMITER, _OldPosition + 1) _CurrentSentence = _UnparsedSentence.Substring(_OldPosition + 1, _Position - _OldPosition - 1) If _CurrentSentence.Length > 0 Then _Satellite.Elevation = Convert.ToInt32(_CurrentSentence) Else _Satellite.Elevation = 0 End If _OldPosition = _Position _Item = "Azimuth" _Position = _UnparsedSentence.IndexOf(NMEA_VALUEDELIMITER, _OldPosition + 1) _CurrentSentence = _UnparsedSentence.Substring(_OldPosition + 1, _Position - _OldPosition - 1) If _CurrentSentence.Length > 0 Then _Satellite.Azimuth = Convert.ToInt32(_CurrentSentence) Else _Satellite.Azimuth = 0 End If _OldPosition = _Position _Item = "Signal to Noise Ratio" _Position = _UnparsedSentence.IndexOf(NMEA_VALUEDELIMITER, _OldPosition + 1) If _Position = -1 Then _Satellite.SNR = 0 _EndSent = True Else _CurrentSentence = _UnparsedSentence.Substring(_OldPosition + 1, _Position - _OldPosition - 1) If _CurrentSentence.Length > 0 Then _Satellite.SNR = Convert.ToInt32(_CurrentSentence) Else _Satellite.SNR = 0 End If _OldPosition = _Position End If If m_Satellites.IndexOf(_Satellite.ID) >= 0 Then m_Satellites(m_Satellites.IndexOf(_Satellite.ID)) = _Satellite ElseIf m_Satellites.Count >= 12 Then Dim _OldestSatIndex As Integer Dim _OldestCreateTime As Date = Now For j As Integer = 0 To m_Satellites.Count - 1 If m_Satellites(j).Timestamp < _OldestCreateTime Then _OldestSatIndex = j _OldestCreateTime = m_Satellites(j).Timestamp End If Next m_Satellites(_OldestSatIndex) = _Satellite Else m_Satellites.Add(_Satellite) End If DrawSatLocation() ' draw satellite location on horizon DrawSatStrength() ' draw satellite signal to noise ratio If _EndSent Then Exit For Next End If Catch ex As Exception Throw New Exception("Error processing GSV. " & _Item & ": " & _CurrentSentence & vbCrLf & ex.Message) End Try End Sub #End Region |
|
#Region "ParseRMC" |
|
' NMEA 0183 RMC sentences contain recommended minimum specific GPS data. ' ' Seq. Field Description ' 1 Sentence ID Talker ID: GP = GPS, Sentence ID: RMC = GPS/transit date ' 2 UTC Time UTC time at which fix was taken. Format: hhmmss.sss ' 3 Status Status: A = Valid, V = Invalid ' 4 Latitude Latitude ddmm.mmmm ' 5 Latitude Hemisphere Latitude hemisphere: N = North, S = South ' 6 Longitude Longitude: dddmm.mmmm ' 7 Longitude Hemisphere Longitude hemisphere: E = East, W = West ' 8 Speed over ground Speed over ground, measured in knots ' 9 Course over ground Course over ground, in degrees (0 to 359.9) ' 10 UTC Date UTC Date in the format DDMMYY ' 11 Magnetic Variation Magnetic variation measured in degrees ' Mag. Variation Hemisphere Magnetic variation hemisphere: E = East, W = West ' 12 Checksum Checksums are optional for most sentences ' ' Samples: ' Signal not acquired: ' $GPRMC,235947.000,V,0000.0000,N,00000.0000,E,,,041299,,*1D ' Signal acquired: ' $GPRMC,092204.999,A,4250.5589,S,14718.5084,E,0.00,89.68,211200,,*25 Public Sub ParseRMC(ByVal rmcSentence As String) Dim _Position As Integer = -1 Dim _OldPosition As Integer = 0 Dim _UnparsedSentence As String = rmcSentence Dim _CurrentSentence As String = String.Empty Dim _Delimiter As String = String.Empty Dim _Item As String = String.Empty Try For i As Integer = 1 To 12 If i = 12 Then _Delimiter = NMEA_CHECKSUMSTARTCHAR Else _Delimiter = NMEA_VALUEDELIMITER End If _Position = _UnparsedSentence.IndexOf(_Delimiter, _OldPosition + 1) _CurrentSentence = _UnparsedSentence.Substring(_OldPosition + 1, _Position - _OldPosition - 1) If _CurrentSentence.Length > 0 Then Select Case i Case 2 _Item = "UTC Time" Time_Label.Text = TimeSerial( _ Convert.ToInt32(_CurrentSentence.Substring(0, 2)), _ Convert.ToInt32(_CurrentSentence.Substring(2, 2)), _ Convert.ToInt32(_CurrentSentence.Substring(4, 2))).ToLongTimeString Case 3 _Item = "Status" Select Case _CurrentSentence Case "V" Status_Label.Text = "Awaiting position fix..." Case "A" Status_Label.Text = "Valid position fix" Case Else Status_Label.Text = "Invalid position fix" End Select Case 4 _Item = "Latitude" Latitude_Label.Text = FormatNumber(ToDegrees(_CurrentSentence), 6) Case 5 _Item = "Latitude Hemisphere" Latitude_Label.Text += " " & _CurrentSentence Case 6 _Item = "Longitude" Longitude_Label.Text = FormatNumber(ToDegrees(_CurrentSentence), 6) Case 7 _Item = "Longitude Hemisphere" Longitude_Label.Text += " " & _CurrentSentence Case 8 _Item = "Speed" Speed_Label.Text = FormatNumber(Convert.ToSingle(_CurrentSentence), 2) Case 9 _Item = "Heading" Bearing_Label.Text = FormatNumber(Convert.ToSingle(_CurrentSentence), 2) If Convert.ToSingle(_CurrentSentence) <> m_Bearing Then m_Bearing = Convert.ToSingle(_CurrentSentence) DrawSatLocation() ' Re-draw bearing bug. End If Case 10 _Item = "UTC Date" Date_Label.Text = DateSerial( _ Convert.ToInt32(_CurrentSentence.Substring(4, 2)), _ Convert.ToInt32(_CurrentSentence.Substring(2, 2)), _ Convert.ToInt32(_CurrentSentence.Substring(0, 2))).ToShortDateString End Select End If _OldPosition = _Position Next Catch ex As Exception Throw New Exception("Error processing RMC. " & _Item & ": " & _CurrentSentence & vbCrLf & ex.Message) End Try End Sub #End Region |
ParseGSV() uses an array (I know. WTF?) of Satellites. This is the Satellite class:
#Region "Satellite" |
|
Option Explicit On Option Strict On Public Class Satellite ' Private instance attributes Private m_SatelliteId As Integer = 0 Private m_Elevation As Integer = 0 Private m_Azimuth As Integer = 0 Private m_SNR As Integer = 0 Private m_Timestamp As Date = Date.MinValue() ' Public instance attributes Public Property Id() As Integer Get Return m_SatelliteId End Get Set(ByVal Value As Integer) m_SatelliteId = Value End Set End Property Public Property Elevation() As Integer Get Return m_Elevation End Get Set(ByVal Value As Integer) m_Elevation = Value End Set End Property Public Property Azimuth() As Integer Get Return m_Azimuth End Get Set(ByVal Value As Integer) m_Azimuth = Value End Set End Property Public Property SNR() As Integer Get Return m_SNR End Get Set(ByVal Value As Integer) m_SNR = Value End Set End Property Public ReadOnly Property Timestamp() As Date Get Return m_Timestamp End Get End Property ' Constructors Public Sub New() MyBase.new() m_Timestamp = Now End Sub End Class #End Region |
[Update 12/7/2011] Amusing contrast with the Windows Phone app I wrote 9 years later. Times have changed 🙄
Compass rose |
< Back to Work | ^ Back to top
All content copyright © Michael Wittenburg 1995 to 2024. All rights reserved.
Merch (t-shirts designed by my twin)