在Windows Mobile和Wince(Windows Embedded CE)下使用.NET Compact Framework进行GPS NMEA data数据分析的开发

简介:

提供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也是同样的方法进行分析,根据文档的描述进行分析。下面为整个类的代码。

 

ContractedBlock.gif NmeaParser

 

参考文献:
http://en.wikipedia.org/wiki/NMEA_0183
GPS Intermediate Driver Reference




    本文转自Jake Lin博客园博客,原文链接:http://www.cnblogs.com/procoder/archive/2009/05/06/Windows-Mobile-GPS-NMEA.html,如需转载请自行联系原作者


相关文章
|
18天前
|
C# Windows
.NET开源免费的Windows快速文件搜索和应用程序启动器
今天大姚给大家分享一款.NET开源(MIT License)、免费、功能强大的Windows快速文件搜索和应用程序启动器:Flow Launcher。
|
18天前
|
存储 文字识别 C#
.NET开源免费、功能强大的 Windows 截图录屏神器
今天大姚给大家分享一款.NET开源免费(基于GPL3.0开源协议)、功能强大、简洁灵活的 Windows 截图、录屏、Gif动图制作神器:ShareX。
|
1月前
|
SQL 开发框架 数据可视化
企业应用开发中.NET EF常用哪种模式?
企业应用开发中.NET EF常用哪种模式?
|
11天前
|
开发框架 前端开发 JavaScript
采用C#.Net +JavaScript 开发的云LIS系统源码 二级医院应用案例有演示
技术架构:Asp.NET CORE 3.1 MVC + SQLserver + Redis等 开发语言:C# 6.0、JavaScript 前端框架:JQuery、EasyUI、Bootstrap 后端框架:MVC、SQLSugar等 数 据 库:SQLserver 2012
|
1月前
|
数据安全/隐私保护 Windows
.net三层架构开发步骤
.net三层架构开发步骤
13 0
|
1月前
深入.net平台的分层开发
深入.net平台的分层开发
62 0
|
1月前
|
Windows
windows server 2019 安装NET Framework 3.5失败,提示:“安装一个或多个角色、角色服务或功能失败” 解决方案
windows server 2019 安装NET Framework 3.5失败,提示:“安装一个或多个角色、角色服务或功能失败” 解决方案
140 0
|
2月前
|
开发框架 前端开发 .NET
福利来袭,.NET Core开发5大案例,30w字PDF文档大放送!!!
为了便于大家查找,特将之前开发的.Net Core相关的五大案例整理成文,共计440页,32w字,免费提供给大家,文章底部有PDF下载链接。
35 1
福利来袭,.NET Core开发5大案例,30w字PDF文档大放送!!!
|
1天前
|
数据采集 数据可视化 数据挖掘
R语言与Python:比较两种数据分析工具
【4月更文挑战第25天】R语言和Python是目前最流行的两种数据分析工具。本文将对这两种工具进行比较,包括它们的历史、特点、应用场景、社区支持、学习资源、性能等方面,以帮助读者更好地了解和选择适合自己的数据分析工具。
|
7天前
|
机器学习/深度学习 数据挖掘 计算机视觉
python数据分析工具SciPy
【4月更文挑战第15天】SciPy是Python的开源库,用于数学、科学和工程计算,基于NumPy扩展了优化、线性代数、积分、插值、特殊函数、信号处理、图像处理和常微分方程求解等功能。它包含优化、线性代数、积分、信号和图像处理等多个模块。通过SciPy,可以方便地执行各种科学计算任务。例如,计算高斯分布的PDF,需要结合NumPy使用。要安装SciPy,可以使用`pip install scipy`命令。这个库极大地丰富了Python在科学计算领域的应用。
12 1