编写你自己的GPS应用程序[转]
在这篇文章的第一部分,我描述了怎样编写一个读取GPS原始NMEA数据的描述器。这篇文章包含VB的源程序,利用这段程序可以准确的确定当前的位置,同步电脑的时钟与原子钟相同,并且可以在多云的天气也可以确定卫星的位置。描述器还可以在手掌设备上工作,而且支持国际上所有的开发者使用。但是,描述器确实还不适合应用与商业,因为它还不够精确。没有精确度作保证,一个应用程序很可能会得出一个不明智的结论,比如说,它会突然告诉司机朝左转进一条小巷,甚至更差。在第二部分,我将详细讲解有关精度方面的内容,并且讨论怎样让GPS应用程序可以胜任汽车导航的工作,以及如何可靠的应用于商业。
引起精度误差的原因
有很多自然现象可以导致精度误差。例如,卫星发射的信号在传递过程中可能被对流层和电离层影响而扭曲。事实上,卫星距离地面很近而且信号还要穿过厚厚的大气层,所以并不能很好地获得稳定数据。一些GPS设备为了避免由此而引起的精度问题,甚至可能排斥这些卫星。
![o_WritingYourOwnGPSApp1.gif](http://www.gisvip.com/uploadfile/article/uploadfile/200809/20080912114751485.gif)
(图 2-1:卫星1的辐射信号在大气层中走的距离更短一些,因此信号的扭曲就小一些。然而,卫星2的水平夹角很小,结果导致严重的大气层扭曲。)
很幸运,大气扭曲是可以被测量的,并且还可以很好的校正数据。这主要是GPS地面卫星的功劳了,它们从固定的位置不断的测量卫星辐射信号的扭曲程度。然后将计算出的校正值与原始信号一同发到GPS接收器上,最终接收器可以即时的校正扭曲。
![o_WritingYourOwnGPSApp2.gif](http://www.gisvip.com/uploadfile/article/uploadfile/200809/20080912114751166.gif)
(图 2-2:通过地面DGPS获得校正信息,然后与卫星1和2的信号混合后,获得正确的扭曲度。)
精度误差可能由于每个卫星的“历表”有细微的不精确而综合引起的。历表是天体坐标与随时间变化的对应表格。如果卫星实际的运行轨迹和历表相背离,那精度自然无从谈起了。这种类型的误差可以采用卫星自己发射小火箭的方法来校正。GPS控制站传送调整的信息给卫星。
![o_WritingYourOwnGPSApp3.gif](http://www.gisvip.com/uploadfile/article/uploadfile/200809/20080912114752500.gif)
(图 2-3:偏离卫星正确的轨道也可以造成精度损失。)
我在文章的第一部分讲过每个GPS卫星有四个原子钟:两个铯钟和两个铷钟,它们每三十万年误差不超过一秒。不过,它们哪怕出现很微小的时间误差都可能引起位置误差,因为这里是通过光速来测距的。地面控制站为将误差缩小到最小,每天上载两次正确的信息。
最后一个影响精度的因素叫“多路径”,主要原因是当一个接收器接收信号是不仅接收了卫星信号,而且还把建筑物和其它障碍物反射的信号也接收了。偏离的信号要走更长的路径到达接收器,并且因此而产生延时。如果接收器使用了这些信号来测量距离,那肯定会超出真实值。很多高级的接收器解决这个问题采用的是仅利用头一条信号探测(最直接的路径),然后抛弃所有延时的信号。
![o_WritingYourOwnGPSApp4.gif](http://www.gisvip.com/uploadfile/article/uploadfile/200809/20080912114752699.gif)
(图 2-4:一个接收器遇到了“多路径”问题,经过反射的信号(红色)和直接辐射信号(绿色)一同被接收。)
使用更加成熟的GPS接收器就可以解决所有的精度问题,这些接收器可以即时获取校正值,比如:WAAS(北美洲)和EGNOS(欧洲)。但是当这些问题与几何降低精度比较时仍会出现一些小问题,这些同样会造成一个足球场那么大的偏差。幸好几何DOP是很容易用正确的编程技术管理。
几何降低精度(Geometric Dilution of Precision)
GPS设备计算你的位置采用一种叫“3维多时段定量法(3-D multilateration)”,这个方法计算出一些圈的交点。在GPS下,没有圈都以卫星为圆心,半径为卫星到GPS设备的距离。理想状态下,这些圈将相交于同一点,从而得出唯一可能的解决方案来获取当前位置。但是事实上,交点会组成一个不规则的形状的区域。设备就在这个区域中的某一点,确定设备的位置必须从这么多方案中选择一个。图2-4显示了一个由三颗卫星产生的区域(用部分$GPGSV语句)。当前的位置可以灰色区域的任一点。精度随着区域的变大而降低,这样就引出了本文的焦点:精度的降低。精度的监测与控制(或简写为DOP)是编写高精度应用程序的重点。
![o_WritingYourOwnGPSApp5.gif](http://www.gisvip.com/uploadfile/article/uploadfile/200809/20080912114753279.gif)
(图 2-5:GPS设备必须从很多种可能的解决方案里确定当前的位置。)
DOP值有三种测量类型:水平、垂直和说明(mean)。水平DOP(或者HDOP)测量它和经纬度的关系作为精度,垂直DOP(或者VDOP)测量他和海拔的关系作为精度,说明DOP(也叫位置DOP,即PDOP),给出一个关于经纬度、海拔精度的完整等级。没个DOP值都是1到50之间数值,其中50 表示非常差的精度,1则表示很理想的精确度。表2-1列出了我划分的一份精确的DOP值细目分类。
DOP分类描述
1 理想 这是最高的信任级别,应用程序可以在任何时候获取最高的精度。
2-3 极好 值得信任的级别,位置的测量相当精确,可以应用于绝大多数应用程序,但是有些非常敏感的应用程序就不行了。
4-6 好 这是用来做商业应用的最低等级。位置的测量可以用来做线路导航建议。
7-8 中等 位置测量可以用来计算,但是质量还应该提高。推荐用语天空观察。
9-20 中下 信任级别很低。位置测量结果应该丢弃或者仅用于粗略估计当前位置。
21-50 差 在这个级别的测量结果是不精确的,大概会有半个足球场大的误差,应该丢弃。
(表 2-1:乔恩关于DOP值的描述。)
回头看图2-5,三颗卫星确定一个大区域的不确定解决方案。这个结果可以用两种方法来改进:天家更多的卫星来定位,甚至用均匀分布在整个天上的卫星。如果把结果改进到现在这个情况那么图2-5看起来像什么?图2-6显示图2-5添加卫星以后的结果。
![o_WritingYourOwnGPSApp6.gif](http://www.gisvip.com/uploadfile/article/uploadfile/200809/20080912114753418.gif)
(图 2-6:三颗以上均匀分布的卫星被添加到图2-5中,从而创建一个高精度的区域,这里的精度损失就会降低。)
现在我们已经了解了精度的机制,那么下一步就是要讲如何确定应用程序需要的真实精度。就经验而论,一个HDOP值小于等于6才被推荐用于应用程序来给用户确定当前位置的建议。例如,当HDOP值大于6时,车载导航系统告诉用户“现在朝左转”就应该忽略位置测量结果。但是6就真的足够好了吗?开发人员怎样在自己的应用程序里使用HDOP值去计算?为了回答这类问题,我拿个简单方程来解释好了:
GPS设备精度 × DOP = 最大允许误差
这个方程使用DOP作为误差的因素之一,同时结合GPS设备的精度得出最大误差允许程度(也就是在该级DOP下特定的可测距离)。其他的经验告诉我们,典型的GPS设备在不加DGPS或者WAAS时精确度一般在5-7米,或者为平均6米。车载导航的HDOP为6并且是典型的GPS设备,那么最大允许误差为 6米 × 6 = 36米,或者118英尺。在一片475平方英尺的市区这个最大允许误差几乎是四分之一的市区了。这个精度可以给用户正确的路线。从另一个角度来看, HDOP值为12就会得出相当与二分之一市区误差的棘轮,这足以让用户拐进小巷引发事故了。因此,利用这个方程,我们说车载导航器使用的HDOP值可以大于6,但不要大多哦。
用这个方程计算现实世界的距离,特别是短而重要的距离。让我们来看看高尔夫运动。是不是高尔夫比汽车要更高的精确度呢?我们需要告诉球员哪个高尔夫洞可以打出最好的球。一些研究表明距离是选择优秀高尔夫球员的重要标准,这里洞与洞之间大概有10-15码的距离。因此,一个高尔夫程序需要不超过10码(9.1米)的允许误差,这样才能保证找到最好的洞。把9.1米作为最大允许误差代入方程,得出最大HDOP为3。因此,高尔夫应用程序需要两倍于汽车导航的精度。
为什么不略过方程而总是把HDOP设为1呢?看起来这很有合理,但是更高的精度需求就要更高的卫星可见度。一个车载导航系统可能无法获得HDOP为1(甚至3)的精度,因为在城市里信号被障碍物阻挡了。如果一定要使HDOP很小,那么这个应用程序会抛出非常多的位置读取,并且这会让司机失去他的耐性。再则说,由于在户外,高尔夫应用程序确实可以获得更小的HDOP值,球员们的PDA可以充分的获取很多均匀分布的卫星信息,除非他们的球飞到了树林里,这就是他们自己的问题了。
总体来讲,想成为成功的GPS软件开发人员就需要使用方程去确定DOP最大的可能值。这将确保应用程序在处理不精确数据时能够使误差最小化。这将使任何GPS应用程序最大化它的价值和功能性。
现在GPS应用程序需要的精度已经可以确定了,是时候去发现哪些源码需要被提取并且获取DOP的最大值了。所有DOP测量结果每隔几秒就记录在$GPGSA语句中。这里有个$GPGSA的例子:
$GPGSA,A,3,11,29,07,08,5,17,24,,,,,,2.3,1.2,2.0*30
一个有经验的GPS应用程序开发人员通过阅读一条$GPGSA语句就可以确定位置信息的精度是否能够被使用。如前所述,最好的DOP率出现在很多卫星均匀分布在不同高度的天空中——GPS设备从多角度获取数据。
该语句的最后三个词是2.3,1.2和2.0,它们描述了说明,水平和垂直DOP。这条语句一个高精度的环境(完全适合于打高尔夫和开车)。使用这篇文章的第一部分最后一个清单(清单 1-8),添加一个叫“ParseGPGSA”的方法到清单里。这个方法提取DOP值并且通过“HDOPReceived”, “VDOPReceived”,“PDOPReceived”三个事件来报告它们。
C#
//*********************************************************************
//** A high-precision NMEA interpreter
//** Written by Jon Person, author of "GPS.NET" (www.gpsdotnet.com)
//*********************************************************************
using System;
using System.Globalization;
public class NmeaInterpreter
{
// Represents the EN-US culture, used for numers in NMEA sentences
public static CultureInfo NmeaCultureInfo = new CultureInfo("en-US");
// Used to convert knots into miles per hour
public static double MPHPerKnot = double.Parse("1.150779",
NmeaCultureInfo);
#region Delegates
public delegate void PositionReceivedEventHandler(string latitude,
string longitude);
public delegate void DateTimeChangedEventHandler(System.DateTime dateTime);
public delegate void BearingReceivedEventHandler(double bearing);
public delegate void SpeedReceivedEventHandler(double speed);
public delegate void SpeedLimitReachedEventHandler();
public delegate void FixObtainedEventHandler();
public delegate void FixLostEventHandler();
public delegate void SatelliteReceivedEventHandler(
int pseudoRandomCode, int azimuth, int elevation,
int signalToNoiseRatio);
public delegate void HDOPReceivedEventHandler(double value);
public delegate void VDOPReceivedEventHandler(double value);
public delegate void PDOPReceivedEventHandler(double value);
#endregion
#region Events
public event PositionReceivedEventHandler PositionReceived;
public event DateTimeChangedEventHandler DateTimeChanged;
public event BearingReceivedEventHandler BearingReceived;
public event SpeedReceivedEventHandler SpeedReceived;
public event SpeedLimitReachedEventHandler SpeedLimitReached;
public event FixObtainedEventHandler FixObtained;
public event FixLostEventHandler FixLost;
public event SatelliteReceivedEventHandler SatelliteReceived;
public event HDOPReceivedEventHandler HDOPReceived;
public event VDOPReceivedEventHandler VDOPReceived;
public event PDOPReceivedEventHandler PDOPReceived;
#endregion
// Processes information from the GPS receiver
public bool Parse(string sentence)
{
// Discard the sentence if its checksum does not match our
// calculated checksum
if (!IsValid(sentence)) return false;
// Look at the first word to decide where to go next
switch (GetWords(sentence)[0])
{
case "$GPRMC":
// A "Recommended Minimum" sentence was found!
return ParseGPRMC(sentence);
case "$GPGSV":
// A "Satellites in View" sentence was recieved
return ParseGPGSV(sentence);
case "$GPGSA":
return ParseGPGSA(sentence);
default:
// Indicate that the sentence was not recognized
return false;
}
}
// Divides a sentence into individual words
public string[] GetWords(string sentence)
{
return sentence.Split(',');
}
// Interprets a $GPRMC message
public bool ParseGPRMC(string sentence)
{
// Divide the sentence into words
string[] Words = GetWords(sentence);
// Do we have enough values to describe our location?
if (Words[3] != "" & Words[4] != "" &
Words[5] != "" & Words[6] != "")
{
// Yes. Extract latitude and longitude
// Append hours
string Latitude = Words[3].Substring(0, 2) + "°";
// Append minutes
Latitude = Latitude + Words[3].Substring(2) + "\"";
// Append hours
Latitude = Latitude + Words[4]; // Append the hemisphere
string Longitude = Words[5].Substring(0, 3) + "°";
// Append minutes
Longitude = Longitude + Words[5].Substring(3) + "\"";
// Append the hemisphere
Longitude = Longitude + Words[6];
// Notify the calling application of the change
if(PositionReceived != null)
PositionReceived(Latitude, Longitude);
}
// Do we have enough values to parse satellite-derived time?
if (Words[1] != "")
{
// Yes. Extract hours, minutes, seconds and milliseconds
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));
}
// Now build a DateTime object with all values
System.DateTime Today = System.DateTime.Now.ToUniversalTime();
System.DateTime SatelliteTime = new System.DateTime(Today.Year,
Today.Month, Today.Day, UtcHours, UtcMinutes, UtcSeconds,
UtcMilliseconds);
// Notify of the new time, adjusted to the local time zone
if(DateTimeChanged != null)
DateTimeChanged(SatelliteTime.ToLocalTime());
}
// Do we have enough information to extract the current speed?
if (Words[7] != "")
{
// Yes. Parse the speed and convert it to MPH
double Speed = double.Parse(Words[7], NmeaCultureInfo) *
MPHPerKnot;
// Notify of the new speed
if(SpeedReceived != null)
SpeedReceived(Speed);
// Are we over the highway speed limit?
if (Speed > 55)
if(SpeedLimitReached != null)
SpeedLimitReached();
}
// Do we have enough information to extract bearing?
if (Words[8] != "")
{
// Indicate that the sentence was recognized
double Bearing = double.Parse(Words[8], NmeaCultureInfo);
if(BearingReceived != null)
BearingReceived(Bearing);
}
// Does the device currently have a satellite fix?
if (Words[2] != "")
{
switch (Words[2])
{
case "A":
if(FixObtained != null)
FixObtained();
break;
case "V":
if(FixLost != null)
FixLost();
break;
}
}
// Indicate that the sentence was recognized
return true;
}
// Interprets a "Satellites in View" NMEA sentence
public bool ParseGPGSV(string sentence)
{
int PseudoRandomCode = 0;
int Azimuth = 0;
int Elevation = 0;
int SignalToNoiseRatio = 0;
// Divide the sentence into words
string[] Words = GetWords(sentence);
// Each sentence contains four blocks of satellite information.
// Read each block and report each satellite's information
int Count = 0;
for (Count = 1; Count <= 4; Count++)
{
// Does the sentence have enough words to analyze?
if ((Words.Length - 1) >= (Count * 4 + 3))
{
// Yes. Proceed with analyzing the block.
// Does it contain any information?
if (Words[Count * 4] != "" & Words[Count * 4 + 1] != ""
& Words[Count * 4 + 2] != "" & Words[Count * 4 + 3] != "")
{
// Yes. Extract satellite information and report it
PseudoRandomCode = System.Convert.ToInt32(Words[Count * 4]);
Elevation = Convert.ToInt32(Words[Count * 4 + 1]);
Azimuth = Convert.ToInt32(Words[Count * 4 + 2]);
SignalToNoiseRatio = Convert.ToInt32(Words[Count * 4 + 2]);
// Notify of this satellite's information
if(SatelliteReceived != null)
SatelliteReceived(PseudoRandomCode, Azimuth,
Elevation, SignalToNoiseRatio);
}
}
}
// Indicate that the sentence was recognized
return true;
}
// Interprets a "Fixed Satellites and DOP" NMEA sentence
public bool ParseGPGSA(string sentence)
{
// Divide the sentence into words
string[] Words = GetWords(sentence);
// update the DOP values
if (Words[15] != "")
{
if(PDOPReceived != null)
PDOPReceived(double.Parse(Words[15], NmeaCultureInfo));
}
if (Words[16] != "")
{
if(HDOPReceived != null)
HDOPReceived(double.Parse(Words[16], NmeaCultureInfo));
}
if (Words[17] != "")
{
if(VDOPReceived != null)
VDOPReceived(double.Parse(Words[17], NmeaCultureInfo));
}
return true;
}
// Returns True if a sentence's checksum matches the
// calculated checksum
public bool IsValid(string sentence)
{
// Compare the characters after the asterisk to the calculation
return sentence.Substring(sentence.IndexOf("*") + 1) ==
GetChecksum(sentence);
}
// Calculates the checksum for a sentence
public string GetChecksum(string sentence)
{
// Loop through all chars to get a checksum
int Checksum = 0;
foreach (char Character in sentence)
{
if (Character == '$')
{
// Ignore the dollar sign
}
else if (Character == '*')
{
// Stop processing before the asterisk
break;
}
else
{
// Is this the first value for the checksum?
if (Checksum == 0)
{
// Yes. Set the checksum to the value
Checksum = Convert.ToByte(Character);
}
else
{
// No. XOR the checksum with this character's value
Checksum = Checksum ^ Convert.ToByte(Character);
}
}
}
// Return the checksum formatted as a two-character hexadecimal
return Checksum.ToString("X2");
}
}
VB.NET
' *********************************************************************
' ** A high-precision NMEA interpreter
' ** Written by Jon Person, author of "GPS.NET" (www.gpsdotnet.com)
' *********************************************************************
Imports System
Imports System.Globalization
Public Class NmeaInterpreter
' Represents the EN-US culture, used for numers in NMEA sentences
Public Shared NmeaCultureInfo As New CultureInfo("en-US")
' Used to convert knots into miles per hour
Public Shared 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)
Public Event HDOPReceived(ByVal value As Double)
Public Event VDOPReceived(ByVal value As Double)
Public Event PDOPReceived(ByVal value As Double)
' 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"
' A "Satellites in View" sentence was recieved
Return ParseGPGSV(sentence)
Case "$GPGSA"
Return ParseGPGSA(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
' Append hours
Dim Latitude As String = Words(3).Substring(0, 2) & "°"
' Append minutes
Latitude = Latitude & Words(3).Substring(2) & """"
' Append the hemisphere
Latitude = Latitude & Words(4)
' Append hours
Dim Longitude As String = Words(5).Substring(0, 3) & "°"
' Append minutes
Longitude = Longitude & Words(5).Substring(3) & """"
' Append the hemisphere
Longitude = Longitude & Words(6)
' 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)
' 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 = Double.Parse(Words(8), NmeaCultureInfo)
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
' Interprets a "Fixed Satellites and DOP" NMEA sentence
Public Function ParseGPGSA(ByVal sentence As String) As Boolean
' Divide the sentence into words
Dim Words() As String = GetWords(sentence)
' update the DOP values
If Words(15) <> "" Then
RaiseEvent PDOPReceived(Double.Parse(Words(15), NmeaCultureInfo))
End If
If Words(16) <> "" Then
RaiseEvent HDOPReceived(Double.Parse(Words(16), NmeaCultureInfo))
End If
If Words(17) <> "" Then
RaiseEvent VDOPReceived(Double.Parse(Words(17), NmeaCultureInfo))
End If
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
确定DOP最大值在整个程序处理中是最简单的部分了,因为加强精度在你最大允许DOP之上的位置测量结果将被忽略。这仅用一个if语句就可以了。为了最好的解释之,我写了一个小的应用程序(这个“DEMO”链接在本文的开始部分),它使用这个NMEA描述器来使最大DOP值为6。
C#
public class HighPrecisionTest
{
private NmeaInterpreter MyInterpreter = new NmeaInterpreter();
private int MaximumDOPAllowed = 6;
private double CurrentHDOP;
public HighPrecisionTest()
{
// Bind events for dilution of position
MyInterpreter.HDOPReceived += new System.EventHandler(OnHDOPReceived);
MyInterpreter.PositionReceived += new System.EventHandler(OnPositionReceived);
}
public void Test()
{
// Parse satellite information (HDOP is 50.0)
MyInterpreter.Parse(
"$GPGSA,A,1,,,,,,,,,,,,,50.0,50.0,50.0*05");
// Parse the current position
MyInterpreter.Parse(
"$GPRMC,225233.990,V,3939.4000,N,10506.4000,W,0.00,51.40,280804,,*35");
// Parse satellite information (HDOP is 1.2)
MyInterpreter.Parse(
"$GPGSA,A,3,11,29,07,08,19,28,26,,,,,,2.3,1.2,2.0*30");
// Parse the current position again
MyInterpreter.Parse(
"$GPRMC,012558.584,A,3939.7000,N,10506.7000,W,0.00,198.07,290804,,*11");
}
private void OnHDOPReceived(double value)
{
// Remember the current HDOP value
CurrentHDOP = value;
}
private void OnPositionReceived(string latitude, string longitude)
{
// Is the HDOP at least six?
if (CurrentHDOP <= MaximumDOPAllowed)
{
// Yes. Display the current position
Debug.WriteLine("You are here: " + latitude + ", " + longitude);
}
else
{
// No. Discard this positional measurement
Debug.WriteLine("The received location is not precise enough to use.");
}
}
}
VB.NET
Public Class HighPrecisionTest
Private WithEvents MyInterpreter As New NmeaInterpreter
Private MaximumDOPAllowed As Integer = 6
Private CurrentHDOP As Double
Public Sub Test()
' Parse satellite information (HDOP is 50.0)
MyInterpreter.Parse("$GPGSA,A,1,,,,,,,,,,,,,50.0,50.0,50.0*05")
' Parse the current position
MyInterpreter.Parse("$GPRMC,225233.990,V,3939.4000,N," & _
"10506.4000,W,0.00,51.40,280804,,*35")
' Parse satellite information (HDOP is 1.2)
MyInterpreter.Parse("$GPGSA,A,3,11,29,07,08,19,28,26,,,,,,2.3,1.2,2.0*30")
' Parse the current position again
MyInterpreter.Parse("$GPRMC,012558.584,A,3939.7000,N," & _
"10506.7000,W,0.00,198.07,290804,,*11")
End Sub
Private Sub OnHDOPReceived(ByVal value As Double) _
Handles MyInterpreter.HDOPReceived
' Remember the current HDOP value
CurrentHDOP = value
End Sub
Private Sub OnPositionReceived(ByVal latitude As String, _
ByVal longitude As String) Handles MyInterpreter.PositionReceived
' Is the HDOP at least six?
If CurrentHDOP <= MaximumDOPAllowed Then
' Yes. Display the current position
Debug.WriteLine("You are here: " & latitude & ", " & longitude)
Else
' No. Discard this positional measurement
Debug.WriteLine("The received location is not precise enough to use.")
End If
End Sub
End Class
这就是它了!你已经可对GPS精度有了很深的理解,并且懂得了怎样去更好地控制它,你现在就可以去开发适合你当地的服务了。
总结
有很多的因素导致GPS卫星信号扭曲。一部分被美国国防局校正,另外一部分被你的GPS接收器利用地面即时校正信息站来校正。剩下来的精度问题就是你要自己去控制GDOP了。控制GDOP是编写商业级专用软件的重要步骤。最大允许误差应该是最大的可能值,它可以在最大化操作环境时最小化精确度问题。
其它的因素是它可以帮助开发人员节省时间。高级GPS接收器技术正推进精度到新的层次。虽然精度对于任何GPS设备消费者都是可疑的,但是很快精度就有可能发展到厘米级了。我相信这个程度的精度将引发工业革命,并为创造奇迹打下基础。我们将看到自动建筑机,世界范围内跟踪每一个集装箱,真正能防止交通阻塞的交通控制系统……以及自动导航的高尔夫球。
引起精度误差的原因
有很多自然现象可以导致精度误差。例如,卫星发射的信号在传递过程中可能被对流层和电离层影响而扭曲。事实上,卫星距离地面很近而且信号还要穿过厚厚的大气层,所以并不能很好地获得稳定数据。一些GPS设备为了避免由此而引起的精度问题,甚至可能排斥这些卫星。
![o_WritingYourOwnGPSApp1.gif](http://www.gisvip.com/uploadfile/article/uploadfile/200809/20080912114751485.gif)
(图 2-1:卫星1的辐射信号在大气层中走的距离更短一些,因此信号的扭曲就小一些。然而,卫星2的水平夹角很小,结果导致严重的大气层扭曲。)
很幸运,大气扭曲是可以被测量的,并且还可以很好的校正数据。这主要是GPS地面卫星的功劳了,它们从固定的位置不断的测量卫星辐射信号的扭曲程度。然后将计算出的校正值与原始信号一同发到GPS接收器上,最终接收器可以即时的校正扭曲。
![o_WritingYourOwnGPSApp2.gif](http://www.gisvip.com/uploadfile/article/uploadfile/200809/20080912114751166.gif)
(图 2-2:通过地面DGPS获得校正信息,然后与卫星1和2的信号混合后,获得正确的扭曲度。)
精度误差可能由于每个卫星的“历表”有细微的不精确而综合引起的。历表是天体坐标与随时间变化的对应表格。如果卫星实际的运行轨迹和历表相背离,那精度自然无从谈起了。这种类型的误差可以采用卫星自己发射小火箭的方法来校正。GPS控制站传送调整的信息给卫星。
![o_WritingYourOwnGPSApp3.gif](http://www.gisvip.com/uploadfile/article/uploadfile/200809/20080912114752500.gif)
(图 2-3:偏离卫星正确的轨道也可以造成精度损失。)
我在文章的第一部分讲过每个GPS卫星有四个原子钟:两个铯钟和两个铷钟,它们每三十万年误差不超过一秒。不过,它们哪怕出现很微小的时间误差都可能引起位置误差,因为这里是通过光速来测距的。地面控制站为将误差缩小到最小,每天上载两次正确的信息。
最后一个影响精度的因素叫“多路径”,主要原因是当一个接收器接收信号是不仅接收了卫星信号,而且还把建筑物和其它障碍物反射的信号也接收了。偏离的信号要走更长的路径到达接收器,并且因此而产生延时。如果接收器使用了这些信号来测量距离,那肯定会超出真实值。很多高级的接收器解决这个问题采用的是仅利用头一条信号探测(最直接的路径),然后抛弃所有延时的信号。
![o_WritingYourOwnGPSApp4.gif](http://www.gisvip.com/uploadfile/article/uploadfile/200809/20080912114752699.gif)
(图 2-4:一个接收器遇到了“多路径”问题,经过反射的信号(红色)和直接辐射信号(绿色)一同被接收。)
使用更加成熟的GPS接收器就可以解决所有的精度问题,这些接收器可以即时获取校正值,比如:WAAS(北美洲)和EGNOS(欧洲)。但是当这些问题与几何降低精度比较时仍会出现一些小问题,这些同样会造成一个足球场那么大的偏差。幸好几何DOP是很容易用正确的编程技术管理。
几何降低精度(Geometric Dilution of Precision)
GPS设备计算你的位置采用一种叫“3维多时段定量法(3-D multilateration)”,这个方法计算出一些圈的交点。在GPS下,没有圈都以卫星为圆心,半径为卫星到GPS设备的距离。理想状态下,这些圈将相交于同一点,从而得出唯一可能的解决方案来获取当前位置。但是事实上,交点会组成一个不规则的形状的区域。设备就在这个区域中的某一点,确定设备的位置必须从这么多方案中选择一个。图2-4显示了一个由三颗卫星产生的区域(用部分$GPGSV语句)。当前的位置可以灰色区域的任一点。精度随着区域的变大而降低,这样就引出了本文的焦点:精度的降低。精度的监测与控制(或简写为DOP)是编写高精度应用程序的重点。
![o_WritingYourOwnGPSApp5.gif](http://www.gisvip.com/uploadfile/article/uploadfile/200809/20080912114753279.gif)
(图 2-5:GPS设备必须从很多种可能的解决方案里确定当前的位置。)
DOP值有三种测量类型:水平、垂直和说明(mean)。水平DOP(或者HDOP)测量它和经纬度的关系作为精度,垂直DOP(或者VDOP)测量他和海拔的关系作为精度,说明DOP(也叫位置DOP,即PDOP),给出一个关于经纬度、海拔精度的完整等级。没个DOP值都是1到50之间数值,其中50 表示非常差的精度,1则表示很理想的精确度。表2-1列出了我划分的一份精确的DOP值细目分类。
DOP分类描述
1 理想 这是最高的信任级别,应用程序可以在任何时候获取最高的精度。
2-3 极好 值得信任的级别,位置的测量相当精确,可以应用于绝大多数应用程序,但是有些非常敏感的应用程序就不行了。
4-6 好 这是用来做商业应用的最低等级。位置的测量可以用来做线路导航建议。
7-8 中等 位置测量可以用来计算,但是质量还应该提高。推荐用语天空观察。
9-20 中下 信任级别很低。位置测量结果应该丢弃或者仅用于粗略估计当前位置。
21-50 差 在这个级别的测量结果是不精确的,大概会有半个足球场大的误差,应该丢弃。
(表 2-1:乔恩关于DOP值的描述。)
回头看图2-5,三颗卫星确定一个大区域的不确定解决方案。这个结果可以用两种方法来改进:天家更多的卫星来定位,甚至用均匀分布在整个天上的卫星。如果把结果改进到现在这个情况那么图2-5看起来像什么?图2-6显示图2-5添加卫星以后的结果。
![o_WritingYourOwnGPSApp6.gif](http://www.gisvip.com/uploadfile/article/uploadfile/200809/20080912114753418.gif)
(图 2-6:三颗以上均匀分布的卫星被添加到图2-5中,从而创建一个高精度的区域,这里的精度损失就会降低。)
现在我们已经了解了精度的机制,那么下一步就是要讲如何确定应用程序需要的真实精度。就经验而论,一个HDOP值小于等于6才被推荐用于应用程序来给用户确定当前位置的建议。例如,当HDOP值大于6时,车载导航系统告诉用户“现在朝左转”就应该忽略位置测量结果。但是6就真的足够好了吗?开发人员怎样在自己的应用程序里使用HDOP值去计算?为了回答这类问题,我拿个简单方程来解释好了:
GPS设备精度 × DOP = 最大允许误差
这个方程使用DOP作为误差的因素之一,同时结合GPS设备的精度得出最大误差允许程度(也就是在该级DOP下特定的可测距离)。其他的经验告诉我们,典型的GPS设备在不加DGPS或者WAAS时精确度一般在5-7米,或者为平均6米。车载导航的HDOP为6并且是典型的GPS设备,那么最大允许误差为 6米 × 6 = 36米,或者118英尺。在一片475平方英尺的市区这个最大允许误差几乎是四分之一的市区了。这个精度可以给用户正确的路线。从另一个角度来看, HDOP值为12就会得出相当与二分之一市区误差的棘轮,这足以让用户拐进小巷引发事故了。因此,利用这个方程,我们说车载导航器使用的HDOP值可以大于6,但不要大多哦。
用这个方程计算现实世界的距离,特别是短而重要的距离。让我们来看看高尔夫运动。是不是高尔夫比汽车要更高的精确度呢?我们需要告诉球员哪个高尔夫洞可以打出最好的球。一些研究表明距离是选择优秀高尔夫球员的重要标准,这里洞与洞之间大概有10-15码的距离。因此,一个高尔夫程序需要不超过10码(9.1米)的允许误差,这样才能保证找到最好的洞。把9.1米作为最大允许误差代入方程,得出最大HDOP为3。因此,高尔夫应用程序需要两倍于汽车导航的精度。
为什么不略过方程而总是把HDOP设为1呢?看起来这很有合理,但是更高的精度需求就要更高的卫星可见度。一个车载导航系统可能无法获得HDOP为1(甚至3)的精度,因为在城市里信号被障碍物阻挡了。如果一定要使HDOP很小,那么这个应用程序会抛出非常多的位置读取,并且这会让司机失去他的耐性。再则说,由于在户外,高尔夫应用程序确实可以获得更小的HDOP值,球员们的PDA可以充分的获取很多均匀分布的卫星信息,除非他们的球飞到了树林里,这就是他们自己的问题了。
总体来讲,想成为成功的GPS软件开发人员就需要使用方程去确定DOP最大的可能值。这将确保应用程序在处理不精确数据时能够使误差最小化。这将使任何GPS应用程序最大化它的价值和功能性。
现在GPS应用程序需要的精度已经可以确定了,是时候去发现哪些源码需要被提取并且获取DOP的最大值了。所有DOP测量结果每隔几秒就记录在$GPGSA语句中。这里有个$GPGSA的例子:
$GPGSA,A,3,11,29,07,08,5,17,24,,,,,,2.3,1.2,2.0*30
一个有经验的GPS应用程序开发人员通过阅读一条$GPGSA语句就可以确定位置信息的精度是否能够被使用。如前所述,最好的DOP率出现在很多卫星均匀分布在不同高度的天空中——GPS设备从多角度获取数据。
该语句的最后三个词是2.3,1.2和2.0,它们描述了说明,水平和垂直DOP。这条语句一个高精度的环境(完全适合于打高尔夫和开车)。使用这篇文章的第一部分最后一个清单(清单 1-8),添加一个叫“ParseGPGSA”的方法到清单里。这个方法提取DOP值并且通过“HDOPReceived”, “VDOPReceived”,“PDOPReceived”三个事件来报告它们。
C#
//*********************************************************************
//** A high-precision NMEA interpreter
//** Written by Jon Person, author of "GPS.NET" (www.gpsdotnet.com)
//*********************************************************************
using System;
using System.Globalization;
public class NmeaInterpreter
{
// Represents the EN-US culture, used for numers in NMEA sentences
public static CultureInfo NmeaCultureInfo = new CultureInfo("en-US");
// Used to convert knots into miles per hour
public static double MPHPerKnot = double.Parse("1.150779",
NmeaCultureInfo);
#region Delegates
public delegate void PositionReceivedEventHandler(string latitude,
string longitude);
public delegate void DateTimeChangedEventHandler(System.DateTime dateTime);
public delegate void BearingReceivedEventHandler(double bearing);
public delegate void SpeedReceivedEventHandler(double speed);
public delegate void SpeedLimitReachedEventHandler();
public delegate void FixObtainedEventHandler();
public delegate void FixLostEventHandler();
public delegate void SatelliteReceivedEventHandler(
int pseudoRandomCode, int azimuth, int elevation,
int signalToNoiseRatio);
public delegate void HDOPReceivedEventHandler(double value);
public delegate void VDOPReceivedEventHandler(double value);
public delegate void PDOPReceivedEventHandler(double value);
#endregion
#region Events
public event PositionReceivedEventHandler PositionReceived;
public event DateTimeChangedEventHandler DateTimeChanged;
public event BearingReceivedEventHandler BearingReceived;
public event SpeedReceivedEventHandler SpeedReceived;
public event SpeedLimitReachedEventHandler SpeedLimitReached;
public event FixObtainedEventHandler FixObtained;
public event FixLostEventHandler FixLost;
public event SatelliteReceivedEventHandler SatelliteReceived;
public event HDOPReceivedEventHandler HDOPReceived;
public event VDOPReceivedEventHandler VDOPReceived;
public event PDOPReceivedEventHandler PDOPReceived;
#endregion
// Processes information from the GPS receiver
public bool Parse(string sentence)
{
// Discard the sentence if its checksum does not match our
// calculated checksum
if (!IsValid(sentence)) return false;
// Look at the first word to decide where to go next
switch (GetWords(sentence)[0])
{
case "$GPRMC":
// A "Recommended Minimum" sentence was found!
return ParseGPRMC(sentence);
case "$GPGSV":
// A "Satellites in View" sentence was recieved
return ParseGPGSV(sentence);
case "$GPGSA":
return ParseGPGSA(sentence);
default:
// Indicate that the sentence was not recognized
return false;
}
}
// Divides a sentence into individual words
public string[] GetWords(string sentence)
{
return sentence.Split(',');
}
// Interprets a $GPRMC message
public bool ParseGPRMC(string sentence)
{
// Divide the sentence into words
string[] Words = GetWords(sentence);
// Do we have enough values to describe our location?
if (Words[3] != "" & Words[4] != "" &
Words[5] != "" & Words[6] != "")
{
// Yes. Extract latitude and longitude
// Append hours
string Latitude = Words[3].Substring(0, 2) + "°";
// Append minutes
Latitude = Latitude + Words[3].Substring(2) + "\"";
// Append hours
Latitude = Latitude + Words[4]; // Append the hemisphere
string Longitude = Words[5].Substring(0, 3) + "°";
// Append minutes
Longitude = Longitude + Words[5].Substring(3) + "\"";
// Append the hemisphere
Longitude = Longitude + Words[6];
// Notify the calling application of the change
if(PositionReceived != null)
PositionReceived(Latitude, Longitude);
}
// Do we have enough values to parse satellite-derived time?
if (Words[1] != "")
{
// Yes. Extract hours, minutes, seconds and milliseconds
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));
}
// Now build a DateTime object with all values
System.DateTime Today = System.DateTime.Now.ToUniversalTime();
System.DateTime SatelliteTime = new System.DateTime(Today.Year,
Today.Month, Today.Day, UtcHours, UtcMinutes, UtcSeconds,
UtcMilliseconds);
// Notify of the new time, adjusted to the local time zone
if(DateTimeChanged != null)
DateTimeChanged(SatelliteTime.ToLocalTime());
}
// Do we have enough information to extract the current speed?
if (Words[7] != "")
{
// Yes. Parse the speed and convert it to MPH
double Speed = double.Parse(Words[7], NmeaCultureInfo) *
MPHPerKnot;
// Notify of the new speed
if(SpeedReceived != null)
SpeedReceived(Speed);
// Are we over the highway speed limit?
if (Speed > 55)
if(SpeedLimitReached != null)
SpeedLimitReached();
}
// Do we have enough information to extract bearing?
if (Words[8] != "")
{
// Indicate that the sentence was recognized
double Bearing = double.Parse(Words[8], NmeaCultureInfo);
if(BearingReceived != null)
BearingReceived(Bearing);
}
// Does the device currently have a satellite fix?
if (Words[2] != "")
{
switch (Words[2])
{
case "A":
if(FixObtained != null)
FixObtained();
break;
case "V":
if(FixLost != null)
FixLost();
break;
}
}
// Indicate that the sentence was recognized
return true;
}
// Interprets a "Satellites in View" NMEA sentence
public bool ParseGPGSV(string sentence)
{
int PseudoRandomCode = 0;
int Azimuth = 0;
int Elevation = 0;
int SignalToNoiseRatio = 0;
// Divide the sentence into words
string[] Words = GetWords(sentence);
// Each sentence contains four blocks of satellite information.
// Read each block and report each satellite's information
int Count = 0;
for (Count = 1; Count <= 4; Count++)
{
// Does the sentence have enough words to analyze?
if ((Words.Length - 1) >= (Count * 4 + 3))
{
// Yes. Proceed with analyzing the block.
// Does it contain any information?
if (Words[Count * 4] != "" & Words[Count * 4 + 1] != ""
& Words[Count * 4 + 2] != "" & Words[Count * 4 + 3] != "")
{
// Yes. Extract satellite information and report it
PseudoRandomCode = System.Convert.ToInt32(Words[Count * 4]);
Elevation = Convert.ToInt32(Words[Count * 4 + 1]);
Azimuth = Convert.ToInt32(Words[Count * 4 + 2]);
SignalToNoiseRatio = Convert.ToInt32(Words[Count * 4 + 2]);
// Notify of this satellite's information
if(SatelliteReceived != null)
SatelliteReceived(PseudoRandomCode, Azimuth,
Elevation, SignalToNoiseRatio);
}
}
}
// Indicate that the sentence was recognized
return true;
}
// Interprets a "Fixed Satellites and DOP" NMEA sentence
public bool ParseGPGSA(string sentence)
{
// Divide the sentence into words
string[] Words = GetWords(sentence);
// update the DOP values
if (Words[15] != "")
{
if(PDOPReceived != null)
PDOPReceived(double.Parse(Words[15], NmeaCultureInfo));
}
if (Words[16] != "")
{
if(HDOPReceived != null)
HDOPReceived(double.Parse(Words[16], NmeaCultureInfo));
}
if (Words[17] != "")
{
if(VDOPReceived != null)
VDOPReceived(double.Parse(Words[17], NmeaCultureInfo));
}
return true;
}
// Returns True if a sentence's checksum matches the
// calculated checksum
public bool IsValid(string sentence)
{
// Compare the characters after the asterisk to the calculation
return sentence.Substring(sentence.IndexOf("*") + 1) ==
GetChecksum(sentence);
}
// Calculates the checksum for a sentence
public string GetChecksum(string sentence)
{
// Loop through all chars to get a checksum
int Checksum = 0;
foreach (char Character in sentence)
{
if (Character == '$')
{
// Ignore the dollar sign
}
else if (Character == '*')
{
// Stop processing before the asterisk
break;
}
else
{
// Is this the first value for the checksum?
if (Checksum == 0)
{
// Yes. Set the checksum to the value
Checksum = Convert.ToByte(Character);
}
else
{
// No. XOR the checksum with this character's value
Checksum = Checksum ^ Convert.ToByte(Character);
}
}
}
// Return the checksum formatted as a two-character hexadecimal
return Checksum.ToString("X2");
}
}
VB.NET
' *********************************************************************
' ** A high-precision NMEA interpreter
' ** Written by Jon Person, author of "GPS.NET" (www.gpsdotnet.com)
' *********************************************************************
Imports System
Imports System.Globalization
Public Class NmeaInterpreter
' Represents the EN-US culture, used for numers in NMEA sentences
Public Shared NmeaCultureInfo As New CultureInfo("en-US")
' Used to convert knots into miles per hour
Public Shared 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)
Public Event HDOPReceived(ByVal value As Double)
Public Event VDOPReceived(ByVal value As Double)
Public Event PDOPReceived(ByVal value As Double)
' 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"
' A "Satellites in View" sentence was recieved
Return ParseGPGSV(sentence)
Case "$GPGSA"
Return ParseGPGSA(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
' Append hours
Dim Latitude As String = Words(3).Substring(0, 2) & "°"
' Append minutes
Latitude = Latitude & Words(3).Substring(2) & """"
' Append the hemisphere
Latitude = Latitude & Words(4)
' Append hours
Dim Longitude As String = Words(5).Substring(0, 3) & "°"
' Append minutes
Longitude = Longitude & Words(5).Substring(3) & """"
' Append the hemisphere
Longitude = Longitude & Words(6)
' 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)
' 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 = Double.Parse(Words(8), NmeaCultureInfo)
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
' Interprets a "Fixed Satellites and DOP" NMEA sentence
Public Function ParseGPGSA(ByVal sentence As String) As Boolean
' Divide the sentence into words
Dim Words() As String = GetWords(sentence)
' update the DOP values
If Words(15) <> "" Then
RaiseEvent PDOPReceived(Double.Parse(Words(15), NmeaCultureInfo))
End If
If Words(16) <> "" Then
RaiseEvent HDOPReceived(Double.Parse(Words(16), NmeaCultureInfo))
End If
If Words(17) <> "" Then
RaiseEvent VDOPReceived(Double.Parse(Words(17), NmeaCultureInfo))
End If
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
确定DOP最大值在整个程序处理中是最简单的部分了,因为加强精度在你最大允许DOP之上的位置测量结果将被忽略。这仅用一个if语句就可以了。为了最好的解释之,我写了一个小的应用程序(这个“DEMO”链接在本文的开始部分),它使用这个NMEA描述器来使最大DOP值为6。
C#
public class HighPrecisionTest
{
private NmeaInterpreter MyInterpreter = new NmeaInterpreter();
private int MaximumDOPAllowed = 6;
private double CurrentHDOP;
public HighPrecisionTest()
{
// Bind events for dilution of position
MyInterpreter.HDOPReceived += new System.EventHandler(OnHDOPReceived);
MyInterpreter.PositionReceived += new System.EventHandler(OnPositionReceived);
}
public void Test()
{
// Parse satellite information (HDOP is 50.0)
MyInterpreter.Parse(
"$GPGSA,A,1,,,,,,,,,,,,,50.0,50.0,50.0*05");
// Parse the current position
MyInterpreter.Parse(
"$GPRMC,225233.990,V,3939.4000,N,10506.4000,W,0.00,51.40,280804,,*35");
// Parse satellite information (HDOP is 1.2)
MyInterpreter.Parse(
"$GPGSA,A,3,11,29,07,08,19,28,26,,,,,,2.3,1.2,2.0*30");
// Parse the current position again
MyInterpreter.Parse(
"$GPRMC,012558.584,A,3939.7000,N,10506.7000,W,0.00,198.07,290804,,*11");
}
private void OnHDOPReceived(double value)
{
// Remember the current HDOP value
CurrentHDOP = value;
}
private void OnPositionReceived(string latitude, string longitude)
{
// Is the HDOP at least six?
if (CurrentHDOP <= MaximumDOPAllowed)
{
// Yes. Display the current position
Debug.WriteLine("You are here: " + latitude + ", " + longitude);
}
else
{
// No. Discard this positional measurement
Debug.WriteLine("The received location is not precise enough to use.");
}
}
}
VB.NET
Public Class HighPrecisionTest
Private WithEvents MyInterpreter As New NmeaInterpreter
Private MaximumDOPAllowed As Integer = 6
Private CurrentHDOP As Double
Public Sub Test()
' Parse satellite information (HDOP is 50.0)
MyInterpreter.Parse("$GPGSA,A,1,,,,,,,,,,,,,50.0,50.0,50.0*05")
' Parse the current position
MyInterpreter.Parse("$GPRMC,225233.990,V,3939.4000,N," & _
"10506.4000,W,0.00,51.40,280804,,*35")
' Parse satellite information (HDOP is 1.2)
MyInterpreter.Parse("$GPGSA,A,3,11,29,07,08,19,28,26,,,,,,2.3,1.2,2.0*30")
' Parse the current position again
MyInterpreter.Parse("$GPRMC,012558.584,A,3939.7000,N," & _
"10506.7000,W,0.00,198.07,290804,,*11")
End Sub
Private Sub OnHDOPReceived(ByVal value As Double) _
Handles MyInterpreter.HDOPReceived
' Remember the current HDOP value
CurrentHDOP = value
End Sub
Private Sub OnPositionReceived(ByVal latitude As String, _
ByVal longitude As String) Handles MyInterpreter.PositionReceived
' Is the HDOP at least six?
If CurrentHDOP <= MaximumDOPAllowed Then
' Yes. Display the current position
Debug.WriteLine("You are here: " & latitude & ", " & longitude)
Else
' No. Discard this positional measurement
Debug.WriteLine("The received location is not precise enough to use.")
End If
End Sub
End Class
这就是它了!你已经可对GPS精度有了很深的理解,并且懂得了怎样去更好地控制它,你现在就可以去开发适合你当地的服务了。
总结
有很多的因素导致GPS卫星信号扭曲。一部分被美国国防局校正,另外一部分被你的GPS接收器利用地面即时校正信息站来校正。剩下来的精度问题就是你要自己去控制GDOP了。控制GDOP是编写商业级专用软件的重要步骤。最大允许误差应该是最大的可能值,它可以在最大化操作环境时最小化精确度问题。
其它的因素是它可以帮助开发人员节省时间。高级GPS接收器技术正推进精度到新的层次。虽然精度对于任何GPS设备消费者都是可疑的,但是很快精度就有可能发展到厘米级了。我相信这个程度的精度将引发工业革命,并为创造奇迹打下基础。我们将看到自动建筑机,世界范围内跟踪每一个集装箱,真正能防止交通阻塞的交通控制系统……以及自动导航的高尔夫球。