I'm the Sr. Software Engineer for an oil and gas company, and we use the DotSpatial.Positioning library to read GPS data from the Qualcomm Gobi 2000 (HP un2420) card built into our Panasonic CF-19 Toughbook laptops. We were getting FormatException(s) from DotSpatial.Positioning when parsing VTG sentences. So, I incorporated the source code into our solution and debugged the code.
I found that the Qualcomm Gobi 2000 (HP un2420) GPS device is returning a VTG sentence with the longitude and speed set to "nan" in the actual sentence read from the COM port. Because the DotSpatial.Positioning library v2.0 uses the double.Parse() method to convert the strings into doubles, the FormatException(s) are being thrown when encountering the "nan" string values.
Although the library handles these exceptions, I've fixed the issue within our copy of the source code and we are no longer getting these exceptions. I'm not sure how the DotSpatial team wants to handle this, but I made the following changes to the source code:
In file NmeaSentence.cs at line 43, we can change the access modifier for _isValid to protected:
protected bool _isValid;
or we could stay within best practices and change the property IsValid to have a protected set accessor, but then the IsValid property of the NmeaSentence and Packet base class would have to be modified:
Packet.cs:
public abstract bool IsValid { get; protected set; }
NmeaSentence.cs:
public override bool IsValid
{
get
{
return _isValid;
}
protected set
{
_isValid = value;
}
}
Either way will work, but we have to be able to set IsValid within the OnSentenceChanged method of the derived GpvtgSentence class. Then, in the OnSentenceChanged method located in the file GpvtgSentence.cs at line 77, we test for invalid strings by using double.TryParse as follows:
protected override void OnSentenceChanged()
{
// First, process the basic info for the sentence
base.OnSentenceChanged();
// Cache the sentence words
string[] words = Words;
int wordCount = words.Length;
// Even if we get a valid VTG sentence, we can have invalid values
if (words != null)
{
double result = 0;
// Check the Longitude - third word of the sentence
if (wordCount > 2 && double.TryParse(words[2], out result) == false)
{
_isValid = false;
return;
}
// Check the Speed - fifth word of the sentence
if (wordCount > 4 && double.TryParse(words[4], out result) == false)
{
_isValid = false;
return;
}
}
...
}
I've also found some mistakes when testing the wordCount to make sure we are not accessing elements of the words string array that do not exist:
protected override void OnSentenceChanged()
{
// First, process the basic info for the sentence
base.OnSentenceChanged();
....
#region Bearing
if (wordCount >= 1 && words[0].Length != 0)
_bearing = Azimuth.Parse(words[0], NmeaCultureInfo);
else
_bearing = Azimuth.Invalid;
#endregion Bearing
#region Magnetic Variation
// This is ok because we are testing wordCount >= 3
// but we could've just tested using wordCount > 2
if (wordCount >= 3 && words[2].Length != 0)
{
_magneticVariation = new Longitude(double.Parse(words[2], NmeaCultureInfo) -
_bearing.DecimalDegrees);
}
else
{
_magneticVariation = Longitude.Invalid;
}
#endregion Magnetic Variation
#region Speed
/* Speed is reported as both knots and KM/H. We can parse either of the values.
* First, try to parse knots. If that fails, parse KM/h.
*/
// This should be testing for wordCount >=5 or just wordCount > 4
if (wordCount > 5 && words[4].Length != 0)
{
_speed = new Speed(
// Parse the numeric portion
double.Parse(words[4], NmeaCultureInfo),
// Use knots
SpeedUnit.Knots);
}
// This should be testing for wordCount >=7 or just wordCount > 6
else if (wordCount > 7 && words[6].Length != 0)
{
_speed = new Speed(
// Parse the numeric portion
double.Parse(words[6], NmeaCultureInfo),
// Use knots
SpeedUnit.KilometersPerHour);
}
else
{
// Invalid speed
_speed = Speed.Invalid;
}
#endregion Speed
}
I added comments about changing the if statements to use the proper testing of the wordCount, but I don't think this is causing any issues because the word count is longer than 7. However, I did make the changes to our version of the code. Now that the OnSentenceChanged method of the GpvtgSentence class is fixed, we can add the last piece of code that checks the sentence's IsValid property and prevents processing invalid sentences. In the OnReadPacket method located in the file NmeaInterpreter.cs at line 279, we need to add a check for the sentence's IsValid property:
protected override void OnReadPacket()
{
// Read a sentence from the underlying stream
NmeaSentence sentence = _stream.ReadTypedSentence();
// Don't process invalid sentences
if (sentence.IsValid == false)
{
return;
}
...
}
There are probably other places in the DotSpatial.Positioning library where this improvement could be made but, since we only use the NmeaInterpreter to read GPS data from our GoBi cards, the above fix is all we needed to resolve the issue.
I have attached the GPS device stream as read by a third party application called VisualGPS that shows the $GPSVTG sentence containing "nan" for the values:
$GPVTG,nan,T,nan,M,0.0,N,0.0,K,A*23
$GPRMC,205238.3,A,3512.585528,N,09725.570975,W,0.0,,100815,,,A*50
$GPGSA,A,3,02,05,06,12,17,,,,,,,,5.1,2.2,4.6*30
$GPGSV,4,1,16,06,57,032,35,05,25,178,30,12,52,309,28,02,71,279,25*7C
$GPGSV,4,2,16,17,27,084,24,09,,,,10,50,108,,24,19,244,*49
$GPGSV,4,3,16,25,15,319,,08,,,,32,,,,31,,,*4A
$GPGSV,4,4,16,30,,,,29,,,,27,,,,26,,,*77
$GPGGA,205239.3,3512.585528,N,09725.570975,W,1,05,2.2,336.2,M,-27.0,M,,*69
$PQXFI,205239.3,3512.585528,N,09725.570975,W,336.2,13.08,20.44,2.08*75
$GPVTG,nan,T,nan,M,0.0,N,0.0,K,A*23
Thank you for writing such a wonderful piece of software. I hope that my analysis will be useful to your team and anyone else who has experienced this issue.
Cleveland E. Raymond
Comments: Cleveland, could you provide your changes as pull request at github? Then we'll merge them into master. https://github.com/DotSpatial/DotSpatial/issues/745
I found that the Qualcomm Gobi 2000 (HP un2420) GPS device is returning a VTG sentence with the longitude and speed set to "nan" in the actual sentence read from the COM port. Because the DotSpatial.Positioning library v2.0 uses the double.Parse() method to convert the strings into doubles, the FormatException(s) are being thrown when encountering the "nan" string values.
Although the library handles these exceptions, I've fixed the issue within our copy of the source code and we are no longer getting these exceptions. I'm not sure how the DotSpatial team wants to handle this, but I made the following changes to the source code:
In file NmeaSentence.cs at line 43, we can change the access modifier for _isValid to protected:
protected bool _isValid;
or we could stay within best practices and change the property IsValid to have a protected set accessor, but then the IsValid property of the NmeaSentence and Packet base class would have to be modified:
Packet.cs:
public abstract bool IsValid { get; protected set; }
NmeaSentence.cs:
public override bool IsValid
{
get
{
return _isValid;
}
protected set
{
_isValid = value;
}
}
Either way will work, but we have to be able to set IsValid within the OnSentenceChanged method of the derived GpvtgSentence class. Then, in the OnSentenceChanged method located in the file GpvtgSentence.cs at line 77, we test for invalid strings by using double.TryParse as follows:
protected override void OnSentenceChanged()
{
// First, process the basic info for the sentence
base.OnSentenceChanged();
// Cache the sentence words
string[] words = Words;
int wordCount = words.Length;
// Even if we get a valid VTG sentence, we can have invalid values
if (words != null)
{
double result = 0;
// Check the Longitude - third word of the sentence
if (wordCount > 2 && double.TryParse(words[2], out result) == false)
{
_isValid = false;
return;
}
// Check the Speed - fifth word of the sentence
if (wordCount > 4 && double.TryParse(words[4], out result) == false)
{
_isValid = false;
return;
}
}
...
}
I've also found some mistakes when testing the wordCount to make sure we are not accessing elements of the words string array that do not exist:
protected override void OnSentenceChanged()
{
// First, process the basic info for the sentence
base.OnSentenceChanged();
....
#region Bearing
if (wordCount >= 1 && words[0].Length != 0)
_bearing = Azimuth.Parse(words[0], NmeaCultureInfo);
else
_bearing = Azimuth.Invalid;
#endregion Bearing
#region Magnetic Variation
// This is ok because we are testing wordCount >= 3
// but we could've just tested using wordCount > 2
if (wordCount >= 3 && words[2].Length != 0)
{
_magneticVariation = new Longitude(double.Parse(words[2], NmeaCultureInfo) -
_bearing.DecimalDegrees);
}
else
{
_magneticVariation = Longitude.Invalid;
}
#endregion Magnetic Variation
#region Speed
/* Speed is reported as both knots and KM/H. We can parse either of the values.
* First, try to parse knots. If that fails, parse KM/h.
*/
// This should be testing for wordCount >=5 or just wordCount > 4
if (wordCount > 5 && words[4].Length != 0)
{
_speed = new Speed(
// Parse the numeric portion
double.Parse(words[4], NmeaCultureInfo),
// Use knots
SpeedUnit.Knots);
}
// This should be testing for wordCount >=7 or just wordCount > 6
else if (wordCount > 7 && words[6].Length != 0)
{
_speed = new Speed(
// Parse the numeric portion
double.Parse(words[6], NmeaCultureInfo),
// Use knots
SpeedUnit.KilometersPerHour);
}
else
{
// Invalid speed
_speed = Speed.Invalid;
}
#endregion Speed
}
I added comments about changing the if statements to use the proper testing of the wordCount, but I don't think this is causing any issues because the word count is longer than 7. However, I did make the changes to our version of the code. Now that the OnSentenceChanged method of the GpvtgSentence class is fixed, we can add the last piece of code that checks the sentence's IsValid property and prevents processing invalid sentences. In the OnReadPacket method located in the file NmeaInterpreter.cs at line 279, we need to add a check for the sentence's IsValid property:
protected override void OnReadPacket()
{
// Read a sentence from the underlying stream
NmeaSentence sentence = _stream.ReadTypedSentence();
// Don't process invalid sentences
if (sentence.IsValid == false)
{
return;
}
...
}
There are probably other places in the DotSpatial.Positioning library where this improvement could be made but, since we only use the NmeaInterpreter to read GPS data from our GoBi cards, the above fix is all we needed to resolve the issue.
I have attached the GPS device stream as read by a third party application called VisualGPS that shows the $GPSVTG sentence containing "nan" for the values:
$GPVTG,nan,T,nan,M,0.0,N,0.0,K,A*23
$GPRMC,205238.3,A,3512.585528,N,09725.570975,W,0.0,,100815,,,A*50
$GPGSA,A,3,02,05,06,12,17,,,,,,,,5.1,2.2,4.6*30
$GPGSV,4,1,16,06,57,032,35,05,25,178,30,12,52,309,28,02,71,279,25*7C
$GPGSV,4,2,16,17,27,084,24,09,,,,10,50,108,,24,19,244,*49
$GPGSV,4,3,16,25,15,319,,08,,,,32,,,,31,,,*4A
$GPGSV,4,4,16,30,,,,29,,,,27,,,,26,,,*77
$GPGGA,205239.3,3512.585528,N,09725.570975,W,1,05,2.2,336.2,M,-27.0,M,,*69
$PQXFI,205239.3,3512.585528,N,09725.570975,W,336.2,13.08,20.44,2.08*75
$GPVTG,nan,T,nan,M,0.0,N,0.0,K,A*23
Thank you for writing such a wonderful piece of software. I hope that my analysis will be useful to your team and anyone else who has experienced this issue.
Cleveland E. Raymond
Comments: Cleveland, could you provide your changes as pull request at github? Then we'll merge them into master. https://github.com/DotSpatial/DotSpatial/issues/745