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

简单的指南针

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.98/5 (17投票s)

2014年6月23日

CPOL

4分钟阅读

viewsIcon

40987

downloadIcon

2448

如何绘制一个指南针并为其提供 NMEA 设备数据(即 GPS 指南针)

引言

此代码展示了如何绘制指南针以及如何将其与兼容 NMEA 的 GPS 设备一起使用。

背景

对我来说,这只是另一个项目的序曲,用于测试如何从 NMEA 设备访问 GPS 数据,并且绘制指南针很有趣。

使用代码

您可以轻松修改代码以支持您自定义的 adruino 电子指南针。指南针类可以独立于数据源重用。只需调用静态 DrawCompass 方法即可绘制指南针。

pictureBox1.Image = Compass.DrawCompass(degree, pitch, 80, tilt, 80, pictureBox1.Size);

关注点

我发现 NMEA 设备似乎不发送标识符,这很令人恼火,所以在打开 Com 端口扫描时,您确实必须等到下一个数据到来,解析它,如果它是 NMEA 数据则接受它,然后关闭所有其他 Com 端口,或者强制用户配置使用的 Com 端口,使其不那么用户友好;-)。 重要的是在退出应用程序时中止所有打开的线程,否则由于线程被阻塞而无法退出。在打开/关闭所有端口时让它们被阻塞是必要的,因为似乎 SerialPort 对象在其他情况下会丢失其事件处理程序,或者尽管仍有引用但对象会自行销毁,并且从另一个线程打开时,thread.Join 似乎可以缓解这个问题,但创建一些用于每个关闭的端口的被阻塞线程,这些线程仍然必须在应用程序结束时中止。我假设这种方法也可能用于其他类型的串行设备。如果您发现此问题的更好解决方案,请给我留言。

    void DisconnectGPS()
        {
            if (serialPort1 != null)
            {
                try
                {
                    if (serialPort1.IsOpen)
                        serialPort1.Close();
                    serialPort1.Dispose();
                }
                catch { }
                //  serialPort1 = null;
            } if (_Serial_Ports != null)
            {
                for (int i = _Serial_Ports.Length - 1; i >= 0; i--)
                {
                    System.IO.Ports.SerialPort p = _Serial_Ports[i];

                    if (p != null)
                    {
                        try
                        {
                            if (p.IsOpen)
                                p.Close();
                            p.Dispose();
                        }
                        catch
                        { }
                        //        p = null;
                    }

                }
            }
            foreach (Thread t in _Gps_Threads)
            {
                t.Abort();
            }
        }
        void ConnectGPS()
        {
            String[] portnames = System.IO.Ports.SerialPort.GetPortNames();
            _Serial_Ports = new System.IO.Ports.SerialPort[portnames.Length];
            _Gps_Threads = new Thread[portnames.Length];
            for (int i = 0; i < portnames.Length; i++)
            {
                System.IO.Ports.SerialPort ssp = new System.IO.Ports.SerialPort(portnames[i]);
                try
                {
                    object data0 = (object)new object[] { ssp, i };
                    System.Threading.Thread t1 = new Thread(delegate(object data)
                    {
                        System.IO.Ports.SerialPort sspt1 = (System.IO.Ports.SerialPort)((object[])data)[0];
                        int it1 = (int)((object[])data)[1];
                        _Serial_Ports[it1] = sspt1;
                        try
                        {
                            sspt1.DataReceived += serialPort1_DataReceived;
                            sspt1.Open();
                        }
                        catch
                        { }
                        System.Threading.Thread.Sleep(3000);
                        try
                        {
                            foreach (System.IO.Ports.SerialPort sspt2 in _Serial_Ports.Where(r => !r.PortName.Equals(serialPort1.PortName)))
                            {
                                if (sspt2.IsOpen)
                                    sspt2.Close();
                                sspt2.Dispose();
                            }
                        }
                        catch
                        { }

                        System.Threading.Thread.CurrentThread.Join();

                    });
                    _Gps_Threads[i] = t1;
                    t1.Start(data0);
                    //   t1.Join();
                }

                catch { }

            }

        }

 

要识别兼容 NMEA 的设备,您只需读出端口传输的数据。它是否与您预期的 NMEA 语句匹配,那么您就找到了它,并设置您的 serialPort1=p。下面的代码适用于 Magellan Explorist 设备。不确定 GPRMC 语句是否随所有 NMEA 设备提供。您可以轻松地对其进行修改以匹配您的设备。NMEA 语句规范,对我来说有效,我在以下位置找到:http://aprs.gids.nl/nmea/ 

我认为这很容易解决。考虑到 NMEA 设备可能会丢失卫星连接,您可能会从设备中的这些行中获得空白。

void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
try
{
  System.IO.Ports.SerialPort p = ((System.IO.Ports.SerialPort)sender);
  string data = p.ReadExisting();
  string[] strArr = data.Split('$');
  for (int i = 0; i < strArr.Length; i++)
  {
   string strTemp = strArr[i];
   string[] nmea = strTemp.Split(',');
   if (nmea[0] == "GPRMC") //Modify to your needs This works with Magellan Explorist GC series
   {
    serialPort1 = p;
    if (!String.IsNullOrEmpty(nmea[8]) )
    {
     degree = Convert.ToDouble(nmea[8]);// -(lines[11] == "E" ? 1 : -1) * (String.IsNullOrEmpty(lines[10]) ? 0 : Convert.ToDouble(lines[10]));//if the device shows true north, else use the deviation to compensate
     pitch = 0;
     tilt = 0;
     pictureBox1.Image = Compass.DrawCompass(degree, pitch, 80, tilt, 80, pictureBox1.Size);
    }
   }
  }
}
catch
{}
}

 

绘制指南针本身非常简单。只需使用一点几何学和 DrawLine、DrawElipse、DrawString...看看使用这些简单的命令您可以做什么。最令人惊奇的是它只运行几毫秒(在我 2 年前的笔记本上,每次 DrawCompass 只有 5 毫秒)。

要将指南针绘制到现有位图上,只需修改该方法,使其不使用“result”而是使用传入的位图,并使用现有位图的大小或您希望指南针的大小,并通过修改 xcenterpoint 和 ycenterpoint 来修改其在位图上的位置就足够了。

使用 GPS 设备时,不使用俯仰和倾斜,因为您从设备的当前位置和移动中获取航向,但如果您使用磁罗盘(例如 adruino 设备),您可能希望在校准时显示俯仰或倾斜,因此我添加了俯仰和倾斜,这样您就可以滚动它,就像在智能手机上一样,并查看它与 0 度俯仰/倾斜滚动的偏差。

  public class Compass
    {
        public static Bitmap DrawCompass(double degree, double pitch, double maxpitch, double tilt, double maxtilt, Size s)
        {

         
                double maxRadius = s.Width > s.Height ? s.Height / 2 : s.Width / 2;

                double sizeMultiplier = maxRadius / 200;
                double relativepitch = pitch / maxpitch;
                double relativetilt = tilt / maxtilt;

                Bitmap result=null;
                SolidBrush drawBrushWhite = new SolidBrush(Color.FromArgb(255, 244, 255));
                SolidBrush drawBrushRed = new SolidBrush(Color.FromArgb(240, 255, 0, 0));
                SolidBrush drawBrushOrange = new SolidBrush(Color.FromArgb(240, 255, 150, 0));
                SolidBrush drawBrushBlue = new SolidBrush(Color.FromArgb(100, 0, 250, 255));
                SolidBrush drawBrushWhiteGrey = new SolidBrush(Color.FromArgb(20, 255, 255, 255));
                double outerradius = (((maxRadius - sizeMultiplier * 60) / maxRadius) * maxRadius);
                double innerradius = (((maxRadius - sizeMultiplier * 90) / maxRadius) * maxRadius);
                double degreeRadius = outerradius + 37 * sizeMultiplier;
                double dirRadius = innerradius - 30 * sizeMultiplier;
                double TriRadius = outerradius + 20 * sizeMultiplier;
                double PitchTiltRadius = innerradius * 0.55;
                if (s.Width * s.Height > 0)
                {
                    result=new Bitmap(s.Width, s.Height);
                using (Font font2 = new Font("Arial", (float)(16 * sizeMultiplier)))
                {
                    using (Font font1 = new Font("Arial", (float)(14 * sizeMultiplier)))
                    {
                        using (Pen penblue = new Pen(Color.FromArgb(100, 0, 250, 255), ((int)(sizeMultiplier) < 4 ? 4 : (int)(sizeMultiplier))))
                        {
                            using (Pen penorange = new Pen(Color.FromArgb(255, 150, 0), ((int)(sizeMultiplier) < 1 ? 1 : (int)(sizeMultiplier))))
                            {
                                using (Pen penred = new Pen(Color.FromArgb(255, 0, 0), ((int)(sizeMultiplier) < 1 ? 1 : (int)(sizeMultiplier))))
                                {

                                    using (Pen pen1 = new Pen(Color.FromArgb(255, 255, 255), (int)(sizeMultiplier * 4)))
                                    {

                                        using (Pen pen2 = new Pen(Color.FromArgb(255, 255, 255), ((int)(sizeMultiplier) < 1 ? 1 : (int)(sizeMultiplier))))
                                        {
                                            using (Pen pen3 = new Pen(Color.FromArgb(0, 255, 255, 255), ((int)(sizeMultiplier) < 1 ? 1 : (int)(sizeMultiplier))))
                                            {
                                                using (Graphics g = Graphics.FromImage(result))
                                                {

                                                    // Calculate some image information.
                                                    double sourcewidth = s.Width;
                                                    double sourceheight = s.Height;

                                                    int xcenterpoint = (int)(s.Width / 2);
                                                    int ycenterpoint = (int)((s.Height / 2));// maxRadius;

                                                    Point pA1 = new Point(xcenterpoint, ycenterpoint - (int)(sizeMultiplier * 45));
                                                    Point pB1 = new Point(xcenterpoint - (int)(sizeMultiplier * 7), ycenterpoint - (int)(sizeMultiplier * 45));
                                                    Point pC1 = new Point(xcenterpoint, ycenterpoint - (int)(sizeMultiplier * 90));
                                                    Point pB2 = new Point(xcenterpoint + (int)(sizeMultiplier * 7), ycenterpoint - (int)(sizeMultiplier * 45));

                                                    Point[] a2 = new Point[] { pA1, pB1, pC1 };
                                                    Point[] a3 = new Point[] { pA1, pB2, pC1 };

                                                    g.DrawPolygon(penred, a2);
                                                    g.FillPolygon(drawBrushRed, a2);
                                                    g.DrawPolygon(penred, a3);
                                                    g.FillPolygon(drawBrushWhite, a3);

                                                    double[] Cos = new double[360];
                                                    double[] Sin = new double[360];

                                                    //draw centercross
                                                    g.DrawLine(pen2, new Point(((int)(xcenterpoint - (PitchTiltRadius - sizeMultiplier * 50))), ycenterpoint), new Point(((int)(xcenterpoint + (PitchTiltRadius - sizeMultiplier * 50))), ycenterpoint));
                                                    g.DrawLine(pen2, new Point(xcenterpoint, (int)(ycenterpoint - (PitchTiltRadius - sizeMultiplier * 50))), new Point(xcenterpoint, ((int)(ycenterpoint + (PitchTiltRadius - sizeMultiplier * 50)))));

                                                    //draw pitchtiltcross
                                                    Point PitchTiltCenter = new Point((int)(xcenterpoint + PitchTiltRadius * relativetilt), (int)(ycenterpoint - PitchTiltRadius * relativepitch));
                                                    int rad = (int)(sizeMultiplier * 8);
                                                    int rad2 = (int)(sizeMultiplier * 25);

                                                    Rectangle r = new Rectangle((int)(PitchTiltCenter.X - rad2), (int)(PitchTiltCenter.Y - rad2), (int)(rad2 * 2), (int)(rad2 * 2));
                                                    g.DrawEllipse(pen3, r);
                                                    g.FillEllipse(drawBrushWhiteGrey, r);
                                                    g.DrawLine(penorange, PitchTiltCenter.X - rad, PitchTiltCenter.Y, PitchTiltCenter.X + rad, PitchTiltCenter.Y);
                                                    g.DrawLine(penorange, PitchTiltCenter.X, PitchTiltCenter.Y - rad, PitchTiltCenter.X, PitchTiltCenter.Y + rad);

                                                    //prep here because need before and after for red triangle.
                                                    for (int d = 0; d < 360; d++)
                                                    {
                                                        //   map[y] = new long[src.Width];
                                                        double angleInRadians = ((((double)d) + 270d) - degree) / 180F * Math.PI;
                                                        Cos[d] = Math.Cos(angleInRadians);
                                                        Sin[d] = Math.Sin(angleInRadians);
                                                    }

                                                    for (int d = 0; d < 360; d++)
                                                    {


                                                        Point p1 = new Point((int)(outerradius * Cos[d]) + xcenterpoint, (int)(outerradius * Sin[d]) + ycenterpoint);
                                                        Point p2 = new Point((int)(innerradius * Cos[d]) + xcenterpoint, (int)(innerradius * Sin[d]) + ycenterpoint);

                                                        //Draw Degree labels
                                                        if (d % 30 == 0)
                                                        {
                                                            g.DrawLine(penblue, p1, p2);

                                                            Point p3 = new Point((int)(degreeRadius * Cos[d]) + xcenterpoint, (int)(degreeRadius * Sin[d]) + ycenterpoint);
                                                            SizeF s1 = g.MeasureString(d.ToString(), font1);
                                                            p3.X = p3.X - (int)(s1.Width / 2);
                                                            p3.Y = p3.Y - (int)(s1.Height / 2);

                                                            g.DrawString(d.ToString(), font1, drawBrushWhite, p3);
                                                            Point pA = new Point((int)(TriRadius * Cos[d]) + xcenterpoint, (int)(TriRadius * Sin[d]) + ycenterpoint);

                                                            int width = (int)(sizeMultiplier * 3);
                                                            int dp = d + width > 359 ? d + width - 360 : d + width;
                                                            int dm = d - width < 0 ? d - width + 360 : d - width;

                                                            Point pB = new Point((int)((TriRadius - (15 * sizeMultiplier)) * Cos[dm]) + xcenterpoint, (int)((TriRadius - (15 * sizeMultiplier)) * Sin[dm]) + ycenterpoint);
                                                            Point pC = new Point((int)((TriRadius - (15 * sizeMultiplier)) * Cos[dp]) + xcenterpoint, (int)((TriRadius - (15 * sizeMultiplier)) * Sin[dp]) + ycenterpoint);

                                                            Pen p = penblue;
                                                            Brush b = drawBrushBlue;
                                                            if (d == 0)
                                                            {
                                                                p = penred;
                                                                b = drawBrushRed;
                                                            }
                                                            Point[] a = new Point[] { pA, pB, pC };

                                                            g.DrawPolygon(p, a);
                                                            g.FillPolygon(b, a);
                                                        }
                                                        else if (d % 2 == 0)
                                                            g.DrawLine(pen2, p1, p2);

                                                        //draw N,E,S,W
                                                        if (d % 90 == 0)
                                                        {
                                                            string dir = (d == 0 ? "N" : (d == 90 ? "E" : (d == 180 ? "S" : "W")));
                                                            Point p4 = new Point((int)(dirRadius * Cos[d]) + xcenterpoint, (int)(dirRadius * Sin[d]) + ycenterpoint);
                                                            SizeF s2 = g.MeasureString(dir, font1);
                                                            p4.X = p4.X - (int)(s2.Width / 2);
                                                            p4.Y = p4.Y - (int)(s2.Height / 2);

                                                            g.DrawString(dir, font1, d == 0 ? drawBrushRed : drawBrushBlue, p4);

                                                            //}
                                                            ////Draw red triangle at 0 degrees
                                                            //if (d == 0)
                                                            //{

                                                        }

                                                    }
                                                    //draw course

                                                    //g.DrawLine(pen1, new Point(xcenterpoint, ycenterpoint - (int)innerradius), new Point(xcenterpoint, ycenterpoint - ((int)outerradius + (int)(sizeMultiplier * 50))));


                                                    String deg = Math.Round(degree, 2).ToString("0.00") + "°";
                                                    SizeF s3 = g.MeasureString(deg, font1);

                                                    g.DrawString(deg, font2, drawBrushOrange, new Point(xcenterpoint - (int)(s3.Width / 2), ycenterpoint - (int)(sizeMultiplier * 40)));

                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
            return result;
        }
    }

 

关于北方的真相

我故意称之为“简单的指南针”,以免打开导航所有其他主题的“潘多拉魔盒”。但既然已经打开了这个盒子,我们不妨提及并指出,GPS 设备通常通过计算 GPS 定位点之间的向量来确定一个人的航向。如果我错了,请纠正我,这意味着 GPS 指南针只能在您移动时工作,除非它有辅助磁传感器,或其他确定航向的方法(例如,蜂窝塔三角测量、无线网络信息等)内置。然而,出于导航目的,为了计算真北,磁北需要进行修正,考虑到磁偏角和磁偏航。(见http://en.wikipedia.org/wiki/Magnetic_deviation)

此外,电子罗盘需要重新校准以补偿环境偏差。

GPS 指南针优点:真北

GPS 指南针缺点:静止时无航向,精度取决于卫星数据接收及其解释

磁罗盘优点:静止时的航向,基于地球磁场

磁罗盘缺点:由于磁偏角和磁偏航,与真北的偏差不准确

例如,我的模拟磁性水肺潜水罗盘会偏向我的手电筒(它有磁性开关),而且我的欧洲水肺潜水罗盘在澳大利亚这里大约偏离 5 度。

显然,我不是这方面的专家,但现在已经提到了。Compass.DrawCompass 功能无论哪种方式都能准确无误地工作。您选择最适合您的指南针 - 数据源并根据您个人适用的偏角/偏航调整数据,然后再绘制指南针,其输入数据的准确性将取决于您。

历史

2014-05-26 根据 SteveHolle 的建议,添加了关于北方的“真相”以及设备特定注意事项的兴趣点。

一个简单的指南针 - CodeProject - 代码之家
© . All rights reserved.