编写自己的 GPS 应用程序:第二部分






4.65/5 (821投票s)
在该系列文章的第二部分中,"GPS.NET" 的作者通过掌握 GPS 精密度概念,向开发者传授如何编写适合现实世界的 GPS 应用程序。源代码包含一个工作的 NMEA 解释器以及 C# 和 VB.NET 中的高精度应用程序示例。

引言
在本文第一部分中,我描述了如何编写一个用于原始 GPS NMEA 数据的解释器。文章中包含 VB.NET 源代码,它利用 GPS 卫星的力量来确定当前位置,将计算机时钟同步到原子时间,并在阴天时指向卫星。该解释器也适用于手持设备,并支持国际开发者。然而,由于该解释器不监控精度,因此不适合商业用途。没有精度,应用程序可能会做出不明智的商业决策,例如意外地指示驾驶员左转进入小巷,甚至更糟。在这第二部分中,我将详细介绍精度,并讨论如何使 GPS 应用程序智能到足以进行车载导航,以及可靠到足以进行商业用途。
精度误差的原因
有几种现象会导致精度差。例如,当卫星无线电信号传输时,它们会被对流层,尤其是电离层所扭曲。事实上,在地平线非常低的卫星不利于获取定位点,因为信号要穿过大气层的大部分。一些 GPS 设备甚至可能排除这些卫星来避免它们造成的精度问题。

幸运的是,大气失真在很大程度上可以被测量和纠正。这是通过使用 GPS 地面站实现的,地面站是固定位置,不断测量卫星无线电信号的失真。然后通过无线电广播计算出的校正信息,当与实际卫星信号结合时,GPS 接收器就能实时纠正失真。

精度误差会因每颗卫星“星历”的微小不准确而加剧。星历是给出天体随时间变化的坐标的表格。如果卫星的实际轨迹偏离其星历,精度会进一步降低。这类误差只能通过卫星本身的小型火箭点火来纠正。调整信息从位于科罗拉多州科泉市施里弗空军基地(Schriever Air Force Base)的 GPS 主控制站传输。

正如我在本文第一部分中提到的,每颗 GPS 卫星都有四台板载原子钟:两台铯原子钟和两台铷原子钟,其精度为每 300,000 年 1 秒!然而,即使是这些时钟的微小误差也会导致位置误差,因为距离是以光速测量的。主控制站通过每天两次向卫星上传校正信息来将这些误差降至最低。
GPS 精度的最后一个损害因素是一种称为“多路径”的效应,当接收器不仅接收卫星信号,还接收到从建筑物和其他障碍物反射回来的额外信号时,就会产生这种效应。反射信号到达接收器的路径更长,因此会延迟。如果接收器使用这些信号,则测量的到卫星的距离会被高估,导致多边测量不准确。更高级的接收器通过仅使用检测到的第一个信号(这是从卫星最直接的路径)来解决多路径问题,然后丢弃任何延迟的信号。

通过使用更复杂的 GPS 接收器,可以解决所有这些精度问题,这些接收器使用实时校正数据,如 WAAS(北美)和 EGNOS(欧洲)。然而,与几何精度衰减(Geometric Dilution of Precision)相比,这些问题造成的精度误差相对较小,GDOP 可能导致接收器误差超过一个美式足球场。幸运的是,通过正确的编程技巧,几何精度衰减是最容易管理的。
几何精度衰减
GPS 设备使用一种称为“3-D 多边测量”(3-D multilateration)的技术来计算您的位置,这是确定多个球体交点的过程。在 GPS 的情况下,每个球体以一颗卫星为中心;球体的半径是从卫星到 GPS 设备的计算距离。理想情况下,这些球体会在同一点精确相交,从而只有一个可能的当前位置解决方案,但实际上,交点会形成一个形状不规则的区域。设备可能位于该区域内的任何一点,迫使设备从多个可能性中选择。图 2-4 显示了由三颗卫星(使用第一部分的 $GPGSV 句子)创建的这样一个区域。当前位置可以是灰色区域内的任何一点。当区域变大时,精度被称为“衰减”,这引出了本文的重点:精度衰减。监测和控制精度衰减(简称 DOP)是编写高精度应用程序的关键。

DOP 值以三种测量类型报告:水平、垂直和平均。水平 DOP(HDOP)测量与纬度和经度相关的 DOP。垂直 DOP(VDOP)测量与海拔相关的精度。平均 DOP,也称为位置 DOP(PDOP),提供纬度、经度和海拔精度的整体评分。每个 DOP 值都报告为一个介于 1 到 50 之间的数字,其中 50 代表精度非常差,1 代表理想精度。表 2-1 列出了我认为的 DOP 值分解。
DOP | 评分 | 描述 |
---|---|---|
1 | 理想 | 这是可能获得的最高置信度级别,用于需要始终具有最高精度的应用程序。 |
2-3 | Excellent | 在此置信度级别,位置测量被认为足够准确,可以满足除最敏感应用程序之外的所有应用程序。 |
4-6 | Good | 表示一个标记为可用于做出商业决策的最低级别的精度。位置测量可用于向用户提供可靠的路线导航建议。 |
7-8 | 中 | 位置测量可用于计算,但定位点质量仍有待提高。建议更开放地查看天空。 |
9-20 | 一般 | 表示低置信度级别。位置测量应被丢弃,或仅用于指示当前位置的大致估计。 |
21-50 | 差 | 在此级别,测量值可能相差半个足球场,应被丢弃。 |
再次看图 2-4,三颗卫星创建了一个大的可能解决方案区域。这种情况可以通过两个因素得到改善:在定位点中添加更多卫星,并使用均匀分布在天空中的卫星。如果情况得到改善,图 2-4 会是什么样子?图 2-5 显示了图 2-4 在添加了另外三颗均匀分布的卫星之后的样子。

确定精度需求
现在已经解释了精度的机制,下一步是弄清楚如何确定应用程序的实际精度需求。总的来说,对于任何根据当前位置进行用户建议的应用程序,建议 HDOP 值小于或等于六。例如,指示用户“立即左转”的车载导航程序,当 HDOP 大于六时,应忽略位置测量。但是六真的够吗?开发者如何确定适用于其应用程序的 HDOP 值?为了回答这些问题,我喜欢使用一个简单的公式
Accuracy of GPS Device * DOP = Maximum Allowable Error
该公式使用 DOP 作为误差因子,当与所使用的 GPS 设备的精度相结合时,可以得到 DOP 水平允许的最大误差,形式为特定的、可测量的距离。另一个经验法则是,典型的消费级 GPS 设备在没有 DGPS 或 WAAS 等增强功能的情况下,精度在 5-7 米之间,平均为六米。使用车载导航 HDOP 为六,以及典型的 GPS 设备,允许的最大误差为 **6 米 \* 6 = 36 米**,即 118 英尺。考虑到市中心的街区大约是 475 英尺见方,允许的最大误差约为一个街区的四分之一。这足以确保驾驶员在正确的道路上转弯。另一方面,HDOP 为十二的允许误差为半个街区(237 英尺),这可能导致驾驶员意外地转入小巷。因此,使用该公式,车载导航可以使用大于六的 HDOP,但不能大太多。
使用此公式的关键是研究实际距离,尤其是最重要的最小距离。举个例子,看看高尔夫。高尔夫是否需要比车载导航更高的精度?高尔夫程序需要告诉用户使用哪根高尔夫球杆来击出最佳一杆。对重要高尔夫距离的一些研究发现,对于大多数球员来说,球杆之间的常规距离间隔约为 10-15 码。因此,高尔夫程序最多只需要 10 码(9.1 米)的允许误差就能持续建议正确的球杆。当 9.1 米作为**最大允许误差**代入公式时,最大 HDOP 计算为 3。因此,高尔夫应用程序需要的精度大约是车载导航系统的两倍。
为什么不跳过公式,总是强制 HDOP 为一呢?这看起来是一个合理的做法,但更高的精度需要更大的卫星可见性。在市中心,由于建筑物遮挡信号,车载导航系统可能无法获得 HDOP 为一(甚至为三)的值。如果强制的 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 句子来知道位置读数是否足够精确以供使用。同样,当有几颗卫星参与定位,并且卫星均匀分布在空中且高度不同——可以说从各个角度照射 GPS 设备时——就会出现最佳的 DOP 评分。
该句子的最后三个词是 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 received
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(
float.Parse(Words[1].Substring(6), NmeaCultureInfo) * 1000);
}
// 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 + 3]);
// 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 received
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(Single.Parse(Words(1).Substring(6), NmeaCultureInfo) * 1000, 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
语句来完成。为了最好地演示这一点,我编写了一个小型应用程序(本文开头链接的“演示”),该应用程序使用 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 是编写商业级 GPS 应用程序的关键。可以通过一个小数学公式来确定特定应用程序允许的最大 DOP。最大允许误差应该是能够最大限度地减少精度问题并最大限度地提高运行条件的最大可能值。
另一个有助于开发者的因素是时间本身。 GPS 接收器技术的进步正在将精度推向新的水平。虽然任何消费级 GPS 设备的精度可能都值得怀疑,但很快就会出现精度达到厘米级的时代。我相信这种精度水平将引发一场行业革命,并为一些真正了不起的事情铺平道路:自动化建筑机械、跟踪世界上每一个集装箱、主动防止交通拥堵的交通控制系统……以及自动导航高尔夫球。
本系列的下一部分将讨论地图绘制,即在您的 .NET 应用程序中显示 GPS 数据和其他地理信息。
- 返回第一部分:编写您自己的 GPS 应用程序
- 继续第三部分:在 .NET 中编写地图和 GIS 软件