轉:http://www.cnblogs.com/procoder/archive/2009/05/05/1450219.html
提供GPS功能的Wince和Windows Mobile都需要一個GPS接收器(GPS Receiver)。GPS receiver就像一個收音機,他從太空中各個GPS衛星(Satellites)接收信号,通過自身的算法(一般在Firmware裡面)計算出位置等資訊,然後以NMEA data的格式輸出。GPS receiver就是接收衛星信号轉換成NMEA data的裝置。
進行GPS的開發需要從GPS receiver取出NMEA data,分析出關心的資料。關心的資料包括經度(Longitude),次元(Latitude)和海拔(Altitude)等等。在Windows Mobile 5以上MS提供了GPS Intermediate Driver,開發人員不再需要自己分析NMEA data了。但是Wince5以及以下版本不提供GPS Intermediate Driver,還是需要自己分析NMEA data來取出關心的資訊。本文講述如何使用C#進行NMEA data的分析。第一眼看,分析NMEA有自己做輪子之嫌,其實了解NMEA的分析也是有好處的,由于各個生産GPS receiver的廠商在硬體工藝和算法的不一樣,各個廠商都提供自己擴充的NMEA data,這些資料GPS Intermediate Driver是不支援的,需要自己分析。
NMEA 全稱NMEA 0183,是電子與資料的通信規範,也就是協定。實作該協定的裝置輸出這種規範的資料,其他應用就可以基于這協定分析出相關的資料。NMEA開始用在航海裝置上,現在廣泛用在GPS裝置上,這就是為什麼NMEA的原始速度使用Knot(海裡/小時)表示。下面是一段GPS NMEA data的範例
$GPRMC, 000006 ,A, 3754.6240 ,S, 14509.7720 ,E, 010.8 , 313.1 , 010108 , 011.8 ,E * 6A
$GPGGA, 201033 , 3754.6240 ,S, 14509.7720 ,E, 1 , 05 , 1.7 , 91.1 ,M, - 1.1 ,M,, * 75
$GPGSA,A, 3 ,, 05 , 10 ,,,, 21 ,, 29 , 30 ,,, 2.9 , 1.7 , 1.3 * 32
$GPGSV, 3 , 3 , 12 , 29 , 74 , 163 , 41 , 30 , 53 , 337 , 40 , 31 , 09 , 266 , 00 , 37 , 00 , 000 , 00 * 78
$PGRME, 6.3 ,M, 11.9 ,M, 13.5 ,M * 25
$PGRMB, 0.0 , 200 ,,,,K,,N,W * 28
$PGRMM,WGS 84 * 06 複制代碼
GPS NMEA data有以下特點:
* 每一條NMEA data的資料都是以dollar符号開頭。
* 從第二個字元開始的前2個字元表示發送者(talker)和接着3個字元表示資料(message)。其中上面的talker中,GP表示通用的GPS NMEA data,而PG為特定廠商的NMEA data。
* 所有資料字段(data fields)都是使用逗号隔開(comma-delimited)。
* 最後一個資料段接着一個星号(asterisk)。
* 星号後面是兩位數字的校正碼(checksum),checksum的計算方法是或計算在 '$' 和 '*'之間的所有字元。
* 最後以回車換行(<CR><LF>)結尾。
有了上述規範,開發NMEA的分析器就變得十分簡單,分析流程是:先接收一條NMEA語句(NMEA sentence),然後檢查語句格式,檢查checksum,然後再根據talker和message進行分發,使用不同的算法進行分析。下面為核心分析流程。
public bool Parse( string sentence)
{
string rawData = sentence;
try
{
if ( ! IsValid(sentence))
{
return false ;
}
sentence = sentence.Substring( 1 , sentence.IndexOf( ' * ' ) - 1 );
string [] Words = Getwords(sentence);
switch (Words[ 0 ])
{
case " GPRMC " :
return ParseGPRMC(Words);
case " GPGGA " :
return ParseGPGGA(Words);
case " GPGSA " :
return ParseGPGSA(Words);
case " GPGSV " :
return ParseGPGSV(Words);
default :
return false ;
}
}
catch (Exception e)
{
Console.WriteLine(e.Message + rawData);
return false ;
}
} 複制代碼
代碼1
Parse為分析接口,所有從GPS Receiver接收到NMEA data全部調用這個接口進行分析。
IsValid檢驗該NMEA sentence是否有效。
Checksum進行Checksum運算,檢驗校驗碼是否有效。
接下來,講述關鍵語句的分析。進行語句的分析,需要一個NMEA的規範手冊,這個手冊可以從GPS廠商下載下傳,例如從Garmin下載下傳 NMEA手冊
在該手冊的第24頁可以看到GPRMC的協定定義。

圖1
根據手冊的規範定義,抽取想要的資訊。如下代碼:
private bool ParseGPRMC( string [] Words)
{
if (Words[ 1 ].Length > 0 & Words[ 9 ].Length > 0 )
{
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(Words[ 1 ].Substring( 7 ));
}
int UtcDay = Convert.ToInt32(Words[ 9 ].Substring( 0 , 2 ));
int UtcMonth = Convert.ToInt32(Words[ 9 ].Substring( 2 , 2 ));
// available for this century
int UtcYear = Convert.ToInt32(Words[ 9 ].Substring( 4 , 2 )) + 2000 ;
utcDateTime = new DateTime(UtcYear, UtcMonth, UtcDay, UtcHours, UtcMinutes, UtcSeconds, UtcMilliseconds);
}
fixStatus = (Words[ 2 ][ 0 ] == ' A ' ) ? FixStatus.Obtained : FixStatus.Lost;
if (Words[ 3 ].Length > 0 & Words[ 4 ].Length == 1 & Words[ 5 ].Length > 0 & Words[ 6 ].Length == 1 )
{
latitude.Hours = int .Parse(Words[ 3 ].Substring( 0 , 2 ));
latitude.Minutes = int .Parse(Words[ 3 ].Substring( 2 , 2 ));
latitude.Seconds = Math.Round( double .Parse(Words[ 3 ].Substring( 5 , 4 )) * 6 / 1000.0 , 3 );
if ( " S " == Words[ 4 ])
{
latitude.Hours = - latitude.Hours;
}
longitude.Hours = int .Parse(Words[ 5 ].Substring( 0 , 3 ));
longitude.Minutes = int .Parse(Words[ 5 ].Substring( 3 , 2 ));
longitude.Seconds = Math.Round( double .Parse(Words[ 5 ].Substring( 6 , 4 )) * 6 / 1000.0 , 3 );
if ( " W " == Words[ 6 ])
{
longitude.Hours = - longitude.Hours;
}
}
if (Words[ 8 ].Length > 0 )
{
azimuth = decimal .Parse(Words[ 8 ], NmeaCultureInfo);
}
if (Words[ 7 ].Length > 0 )
{
velocity = decimal .Parse(Words[ 7 ], NmeaCultureInfo) * KMpHPerKnot;
}
return true ;
} 複制代碼
代碼2
從ParseGPRMC看,傳遞的參數是一個string的數組,分析要做的事情就是把數組的元素根據手冊翻譯成需要的資訊,例如字段1為UTC的日期資訊,字段9為UTC的時間資訊。字段2為fix資訊,就是GPS是否完成了初始化的資訊。字段3,4為經度,字段5,6為次元。字段7為速度。字段8為角度。
private bool ParseGPGGA( string [] Words)
{
if (Words[ 6 ].Length > 0 )
{
switch (Convert.ToInt32(Words[ 6 ]))
{
case 0 :
differentialGpsType = DifferentialGpsType.NotSet;
break ;
case 1 :
differentialGpsType = DifferentialGpsType.SPS;
break ;
case 2 :
differentialGpsType = DifferentialGpsType.DSPS;
break ;
case 3 :
differentialGpsType = DifferentialGpsType.PPS;
break ;
case 4 :
differentialGpsType = DifferentialGpsType.RTK;
break ;
default :
differentialGpsType = DifferentialGpsType.NotSet;
break ;
}
}
if (Words[ 7 ].Length > 0 )
{
satellitesInUsed = Convert.ToInt32(Words[ 7 ]);
}
if (Words[ 8 ].Length > 0 )
{
horizontalDilutionOfPrecision = Convert.ToDecimal(Words[ 8 ]);
}
if (Words[ 9 ].Length > 0 )
{
altitude = Convert.ToDecimal(Words[ 9 ]);
}
return true ;
} 複制代碼
代碼3
分析ParseGPGGA和分析ParseGPRMC一樣,從數組抽取資訊,字段6為fix類型,這個參數表示使用了那些輔佐衛星或者地面信号站來提高GPS的精度。SPS為普通類型,DSPS使用了DGPS 地面信号站fix,DSPS使用了WAAS位置衛星fix(隻是用在美國),PPS使用了EGNOS位置衛星fix(隻是用在歐洲),RTK使用了MSAS位置衛星fix(隻是用在亞洲)。字段7為使用衛星的數量。字段8為水準精度。字段9為海拔。
private bool ParseGPGSA( string [] Words)
{
if (Words[ 1 ].Length > 0 )
{
fixMode = Words[ 1 ][ 0 ] == ' A ' ? FixMode.Auto : FixMode.Manual;
}
if (Words[ 2 ].Length > 0 )
{
switch (Convert.ToInt32(Words[ 2 ]))
{
case 1 :
fixMethod = FixMethod.NotSet;
break ;
case 2 :
fixMethod = FixMethod.Fix2D;
break ;
case 3 :
fixMethod = FixMethod.Fix3D;
break ;
default :
fixMethod = FixMethod.NotSet;
break ;
}
}
foreach (GpsSatellite s in satellites.Values)
{
s.InUsed = false ;
}
satellitesInUsed = 0 ;
for ( int i = 0 ; i < 12 ; ++ i)
{
string id = Words[ 3 + i];
if (id.Length > 0 )
{
int nId = Convert.ToInt32(id);
if ( ! satellites.ContainsKey(nId))
{
satellites[nId] = new GpsSatellite();
satellites[nId].PRC = nId;
}
satellites[nId].InUsed = true ;
++ satellitesInUsed;
}
}
if (Words[ 15 ].Length > 0 )
{
positionDilutionOfPrecision = Convert.ToDecimal(Words[ 15 ]);
}
if (Words[ 16 ].Length > 0 )
{
horizontalDilutionOfPrecision = Convert.ToDecimal(Words[ 16 ]);
}
if (Words[ 17 ].Length > 0 )
{
verticalDilutionOfPrecision = Convert.ToDecimal(Words[ 17 ]);
}
return true ;
} 複制代碼
代碼4
從ParseGPGSA看,字段1為fix的狀态,手工fix或者自動fix。字段2為fix的方法,2D或者3D。字段3到14共12個字段分别為在使用衛星的資訊。字段15為位置精度資訊。字段16為水準精度資訊。字段17為垂直精度資訊。
private bool ParseGPGSV( string [] Words)
{
int messageNumber = 0 ;
if (Words[ 2 ].Length > 0 )
{
messageNumber = Convert.ToInt32(Words[ 2 ]);
}
if (Words[ 3 ].Length > 0 )
{
satellitesInView = Convert.ToInt32(Words[ 3 ]);
}
if (messageNumber == 0 || satellitesInView == 0 )
{
return false ;
}
for ( int i = 1 ; i <= 4 ; ++ i)
{
if ((Words.Length - 1 ) >= (i * 4 + 3 ))
{
int nId = 0 ;
if (Words[i * 4 ].Length > 0 )
{
string id = Words[i * 4 ];
nId = Convert.ToInt32(id);
if ( ! satellites.ContainsKey(nId))
{
satellites[nId] = new GpsSatellite();
satellites[nId].PRC = nId;
}
satellites[nId].InView = true ;
}
if (Words[i * 4 + 1 ].Length > 0 )
{
satellites[nId].Elevation = Convert.ToInt32(Words[i * 4 + 1 ]);
}
if (Words[i * 4 + 2 ].Length > 0 )
{
satellites[nId].Azimuth = Convert.ToInt32(Words[i * 4 + 2 ]);
}
if (Words[i * 4 + 3 ].Length > 0 )
{
satellites[nId].SNR = Convert.ToInt32(Words[i * 4 + 3 ]);
satellites[nId].NotTracking = false ;
}
else
{
satellites[nId].NotTracking = true ;
}
}
}
return true ;
} 複制代碼
代碼5
從ParseGPGSA看,這個比較特别,他把在使用的衛星資訊分開多條語句output。如下:
$GPGSV, 3 , 1 , 12 , 03 , 43 , 246 , 46 , 06 , 57 , 263 , 52 , 09 , 10 , 090 , 00 , 14 , 29 , 357 , 41 * 71
$GPGSV, 3 , 2 , 12 , 15 , 12 , 140 , 00 , 16 , 10 , 307 , 00 , 18 , 59 , 140 , 00 , 19 , 20 , 224 , 00 * 75
$GPGSV, 3 , 3 , 12 , 21 , 48 , 089 , 00 , 22 , 69 , 265 , 36 , 24 , 09 , 076 , 00 , 34 , 00 , 000 , 00 * 76
複制代碼
字段1為一共分開多少條語句。字段2為目前語句的序号。字段3為在使用的衛星的數量。後面字段分别表示三個不同衛星的資訊,取其中一個衛星來解釋,字段4為衛星的ID,字段5為太空海拔,字段6為角度,字段7為信号強弱。
對于廠商的私有NMEA data也是同樣的方法進行分析,根據文檔的描述進行分析。下面為整個類的代碼。
NmeaParser
<!--
Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/
--> public class NmeaParser
{
public struct Coordinate
{
public int Hours;
public int Minutes;
public double Seconds;
}
public enum FixStatus
{
NotSet,
Obtained, //A
Lost //V
}
public enum FixMode
{
Auto, //A
Manual
}
public enum FixMethod
{
NotSet,
Fix2D,
Fix3D
}
public enum DifferentialGpsType
{
NotSet,
SPS,
DSPS,
PPS,
RTK
}
public class GpsSatellite
{
public int PRC { get; set; }
public int Elevation { get; set; }
public int Azimuth { get; set; }
public int SNR { get; set; }
public bool InUsed { get; set; }
public bool InView { get; set; }
public bool NotTracking { get; set; }
}
private static readonly CultureInfo NmeaCultureInfo = new CultureInfo("en-US");
private static readonly decimal KMpHPerKnot = decimal.Parse("1.852", NmeaCultureInfo);
private Coordinate latitude;
private Coordinate longitude;
private decimal altitude = 0;
private DateTime utcDateTime;
private decimal velocity = 0;
private decimal azimuth = 0;
private FixStatus fixStatus;
private DifferentialGpsType differentialGpsType;
private FixMode fixMode;
private FixMethod fixMethod;
private int satellitesInView;
private int satellitesInUsed;
private readonly Dictionary<int, GpsSatellite> satellites;
private decimal horizontalDilutionOfPrecision = 50;
private decimal positionDilutionOfPrecision = 50;
private decimal verticalDilutionOfPrecision = 50;
public NmeaParser()
{
satellites = new Dictionary<int, GpsSatellite>();
}
public bool Parse(string sentence)
{
string rawData = sentence;
try
{
if (!IsValid(sentence))
{
return false;
}
sentence = sentence.Substring(1, sentence.IndexOf('*') - 1);
string[] Words = Getwords(sentence);
switch (Words[0])
{
case "GPRMC":
return ParseGPRMC(Words);
case "GPGGA":
return ParseGPGGA(Words);
case "GPGSA":
return ParseGPGSA(Words);
case "GPGSV":
return ParseGPGSV(Words);
default:
return false;
}
}
catch (Exception e)
{
Console.WriteLine(e.Message + rawData);
return false;
}
}
private bool IsValid(string sentence)
{
// GPS data can't be zero length
if (sentence.Length == 0)
{
return false;
}
// first character must be a $
if (sentence[0] != '$')
{
return false;
}
// GPS data can't be longer than 82 character
if (sentence.Length > 82)
{
return false;
}
try
{
string checksum = sentence.Substring(sentence.IndexOf('*') + 1);
return Checksum(sentence, checksum);
}
catch (Exception e)
{
Console.WriteLine("Checksum failure. " + e.Message);
return false;
}
}
private bool Checksum(string sentence, string checksumStr)
{
int checksum = 0;
int length = sentence.IndexOf('*') - 1;
// go from first character upto last *
for (int i = 1; i <= length; ++i)
{
checksum = checksum ^ Convert.ToByte(sentence[i]);
}
return (checksum.ToString("X2") == checksumStr);
}
// Divides a sentence into individual Words
private static string[] Getwords(string sentence)
{
return sentence.Split(',');
}
private bool ParseGPRMC(string[] Words)
{
if (Words[1].Length > 0 & Words[9].Length > 0)
{
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(Words[1].Substring(7));
}
int UtcDay = Convert.ToInt32(Words[9].Substring(0, 2));
int UtcMonth = Convert.ToInt32(Words[9].Substring(2, 2));
// available for this century
int UtcYear = Convert.ToInt32(Words[9].Substring(4, 2)) + 2000;
utcDateTime = new DateTime(UtcYear, UtcMonth, UtcDay, UtcHours, UtcMinutes, UtcSeconds, UtcMilliseconds);
}
fixStatus = (Words[2][0] == 'A') ? FixStatus.Obtained : FixStatus.Lost;
if (Words[3].Length > 0 & Words[4].Length == 1 & Words[5].Length > 0 & Words[6].Length == 1)
{
latitude.Hours = int.Parse(Words[3].Substring(0, 2));
latitude.Minutes = int.Parse(Words[3].Substring(2, 2));
latitude.Seconds = Math.Round(double.Parse(Words[3].Substring(5, 4)) * 6 / 1000.0, 3);
if ("S" == Words[4])
{
latitude.Hours = -latitude.Hours;
}
longitude.Hours = int.Parse(Words[5].Substring(0, 3));
longitude.Minutes = int.Parse(Words[5].Substring(3, 2));
longitude.Seconds = Math.Round(double.Parse(Words[5].Substring(6, 4)) * 6 / 1000.0, 3);
if ("W" == Words[6])
{
longitude.Hours = -longitude.Hours;
}
}
if (Words[8].Length > 0)
{
azimuth = decimal.Parse(Words[8], NmeaCultureInfo);
}
if (Words[7].Length > 0)
{
velocity = decimal.Parse(Words[7], NmeaCultureInfo) * KMpHPerKnot;
}
return true;
}
private bool ParseGPGGA(string[] Words)
{
if (Words[6].Length > 0)
{
switch (Convert.ToInt32(Words[6]))
{
case 0:
differentialGpsType = DifferentialGpsType.NotSet;
break;
case 1:
differentialGpsType = DifferentialGpsType.SPS;
break;
case 2:
differentialGpsType = DifferentialGpsType.DSPS;
break;
case 3:
differentialGpsType = DifferentialGpsType.PPS;
break;
case 4:
differentialGpsType = DifferentialGpsType.RTK;
break;
default:
differentialGpsType = DifferentialGpsType.NotSet;
break;
}
}
if (Words[7].Length > 0)
{
satellitesInUsed = Convert.ToInt32(Words[7]);
}
if (Words[8].Length > 0)
{
horizontalDilutionOfPrecision = Convert.ToDecimal(Words[8]);
}
if (Words[9].Length > 0)
{
altitude = Convert.ToDecimal(Words[9]);
}
return true;
}
private bool ParseGPGSA(string[] Words)
{
if (Words[1].Length > 0)
{
fixMode = Words[1][0] == 'A' ? FixMode.Auto : FixMode.Manual;
}
if (Words[2].Length > 0)
{
switch (Convert.ToInt32(Words[2]))
{
case 1:
fixMethod = FixMethod.NotSet;
break;
case 2:
fixMethod = FixMethod.Fix2D;
break;
case 3:
fixMethod = FixMethod.Fix3D;
break;
default:
fixMethod = FixMethod.NotSet;
break;
}
}
foreach (GpsSatellite s in satellites.Values)
{
s.InUsed = false;
}
satellitesInUsed = 0;
for (int i = 0; i < 12; ++i)
{
string id = Words[3 + i];
if (id.Length > 0)
{
int nId = Convert.ToInt32(id);
if (!satellites.ContainsKey(nId))
{
satellites[nId] = new GpsSatellite();
satellites[nId].PRC = nId;
}
satellites[nId].InUsed = true;
++satellitesInUsed;
}
}
if (Words[15].Length > 0)
{
positionDilutionOfPrecision = Convert.ToDecimal(Words[15]);
}
if (Words[16].Length > 0)
{
horizontalDilutionOfPrecision = Convert.ToDecimal(Words[16]);
}
if (Words[17].Length > 0)
{
verticalDilutionOfPrecision = Convert.ToDecimal(Words[17]);
}
return true;
}
private bool ParseGPGSV(string[] Words)
{
int messageNumber = 0;
if (Words[2].Length > 0)
{
messageNumber = Convert.ToInt32(Words[2]);
}
if (Words[3].Length > 0)
{
satellitesInView = Convert.ToInt32(Words[3]);
}
if (messageNumber == 0 || satellitesInView == 0)
{
return false;
}
for (int i = 1; i <= 4; ++i)
{
if ((Words.Length - 1) >= (i * 4 + 3))
{
int nId = 0;
if (Words[i * 4].Length > 0)
{
string id = Words[i * 4];
nId = Convert.ToInt32(id);
if (!satellites.ContainsKey(nId))
{
satellites[nId] = new GpsSatellite();
satellites[nId].PRC = nId;
}
satellites[nId].InView = true;
}
if (Words[i * 4 + 1].Length > 0)
{
satellites[nId].Elevation = Convert.ToInt32(Words[i * 4 + 1]);
}
if (Words[i * 4 + 2].Length > 0)
{
satellites[nId].Azimuth = Convert.ToInt32(Words[i * 4 + 2]);
}
if (Words[i * 4 + 3].Length > 0)
{
satellites[nId].SNR = Convert.ToInt32(Words[i * 4 + 3]);
satellites[nId].NotTracking = false;
}
else
{
satellites[nId].NotTracking = true;
}
}
}
return true;
}
}