65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.65/5 (821投票s)

2004年12月20日

CPOL

12分钟阅读

viewsIcon

1228719

downloadIcon

6779

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

Sample Image - WritingGPSApplications2.jpg

引言

本文第一部分中,我描述了如何编写一个用于原始 GPS NMEA 数据的解释器。文章中包含 VB.NET 源代码,它利用 GPS 卫星的力量来确定当前位置,将计算机时钟同步到原子时间,并在阴天时指向卫星。该解释器也适用于手持设备,并支持国际开发者。然而,由于该解释器不监控精度,因此不适合商业用途。没有精度,应用程序可能会做出不明智的商业决策,例如意外地指示驾驶员左转进入小巷,甚至更糟。在这第二部分中,我将详细介绍精度,并讨论如何使 GPS 应用程序智能到足以进行车载导航,以及可靠到足以进行商业用途。

精度误差的原因

有几种现象会导致精度差。例如,当卫星无线电信号传输时,它们会被对流层,尤其是电离层所扭曲。事实上,在地平线非常低的卫星不利于获取定位点,因为信号要穿过大气层的大部分。一些 GPS 设备甚至可能排除这些卫星来避免它们造成的精度问题。

图 2-1:卫星 1 的无线电信号穿过的大气层较少,因此失真较小。然而,卫星 2 低于地平线,导致严重的 theospheric 失真。

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

图 2-2:无线电信号的失真通过将卫星信号(1 和 2)与通过 DGPS 地面站(3 和 4)传输的校正信息相结合来纠正。

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

图 2-3:卫星实际轨道路径的偏差也会导致精度损失。

正如我在本文第一部分中提到的,每颗 GPS 卫星都有四台板载原子钟:两台铯原子钟和两台铷原子钟,其精度为每 300,000 年 1 秒!然而,即使是这些时钟的微小误差也会导致位置误差,因为距离是以光速测量的。主控制站通过每天两次向卫星上传校正信息来将这些误差降至最低。

GPS 精度的最后一个损害因素是一种称为“多路径”的效应,当接收器不仅接收卫星信号,还接收到从建筑物和其他障碍物反射回来的额外信号时,就会产生这种效应。反射信号到达接收器的路径更长,因此会延迟。如果接收器使用这些信号,则测量的到卫星的距离会被高估,导致多边测量不准确。更高级的接收器通过仅使用检测到的第一个信号(这是从卫星最直接的路径)来解决多路径问题,然后丢弃任何延迟的信号。

图 2-4:接收器因“多路径”而感到困惑,接收到几个反射信号(红色)以及直接无线电信号(绿色)。

通过使用更复杂的 GPS 接收器,可以解决所有这些精度问题,这些接收器使用实时校正数据,如 WAAS(北美)和 EGNOS(欧洲)。然而,与几何精度衰减(Geometric Dilution of Precision)相比,这些问题造成的精度误差相对较小,GDOP 可能导致接收器误差超过一个美式足球场。幸运的是,通过正确的编程技巧,几何精度衰减是最容易管理的。

几何精度衰减

GPS 设备使用一种称为“3-D 多边测量”(3-D multilateration)的技术来计算您的位置,这是确定多个球体交点的过程。在 GPS 的情况下,每个球体以一颗卫星为中心;球体的半径是从卫星到 GPS 设备的计算距离。理想情况下,这些球体会在同一点精确相交,从而只有一个可能的当前位置解决方案,但实际上,交点会形成一个形状不规则的区域。设备可能位于该区域内的任何一点,迫使设备从多个可能性中选择。图 2-4 显示了由三颗卫星(使用第一部分的 $GPGSV 句子)创建的这样一个区域。当前位置可以是灰色区域内的任何一点。当区域变大时,精度被称为“衰减”,这引出了本文的重点:精度衰减。监测和控制精度衰减(简称 DOP)是编写高精度应用程序的关键。

图 2-5:GPS 设备必须从几个可能的当前位置解决方案中选择一个。

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-1:Jon 对精度衰减值的解释。

再次看图 2-4,三颗卫星创建了一个大的可能解决方案区域。这种情况可以通过两个因素得到改善:在定位点中添加更多卫星,并使用均匀分布在天空中的卫星。如果情况得到改善,图 2-4 会是什么样子?图 2-5 显示了图 2-4 在添加了另外三颗均匀分布的卫星之后的样子。

图 2-6:在图 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 值并通过三个事件报告:HDOPReceivedVDOPReceivedPDOPReceived

[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 数据和其他地理信息。

© . All rights reserved.