2.2.6 电子海图系统解析及开发 海图显示 - 绘制EBL/VRM

EBL/VRM是电子海图系统很常见的功能。至少应具有如下功能:

  1. 显示EBL/VRM;
  2. 如果存在当前船位,EBL/VRM的初始中心是船位,否则是屏幕中心;
  3. 按住鼠标可拖拽调整EBL的方位;
  4. 按住鼠标可拖拽调整VRM的大小;
  5. 按住鼠标可拖拽调整EBL/VRM中心的位置,
  6. 与鼠标平移、缩放操作不冲突。

判断点或线是否移动,都需要计算鼠标位置与点、线之间的距离。当距离小于一定范围(4像素)即可认为需要移动。绘制EBL/VRM过程中,涉及到的距离都是用的屏幕坐标。其中计算点到直线的距离用到的公式为:

点到直接的距离公式

GeoTools里添加屏幕距离公式相关代码(考虑到开方操作较为复杂,实际返回的为距离的平方数)。

        //距离范围及平方
        public const int DisTorelence = 4;
        public const int DisTorelenceSqrd = 4*4;

        //屏幕上两点之间的距离的平方
        public static double DistanceSqrd(int x1, int y1, int x2, int y2)
        {
            return (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1);
        }

        public static double DistanceSqrd(Point pt1, Point pt2)
        {
            return DistanceSqrd(pt1.X, pt1.Y, pt2.X, pt2.Y);
        }

        //屏幕上点到直线的距离的平方,直线用两坐标点表示
        public static double DistanceFromLineSqrd(Point pt, int x1, int y1, int x2, int y2)
        {
            if (x1 == x2 && y1 == y2) return DistanceSqrd(pt.X, pt.Y, x1, y1);

            var a = y2 - y1;
            var b = x2 - x1;
            var c = a * pt.X - b * pt.Y + x2 * y1 - y2 * x1;
            return c * c / (a * a + b * b);
        }

        public static double DistanceFromLineSqrd(Point pt, Point pt1, Point pt2)
        {
            return DistanceFromLineSqrd(pt, pt1.X, pt1.Y, pt2.X, pt2.Y);
        }
  1. 窗体添加如下EBL/VRM相关字段:
        private S57Pos2D eblVrmCenterPos;                           //中心点地理坐标
        private S57Pos2D eblVrmOtherPos;                            //VRM上一点的地理坐标,计算距离用
        private Point eblVrmCenterPoint;                            //中心点屏幕坐标
        private Point eblVrmOtherPoint;                             //VRM上一点的屏幕坐标,计算距离用
        private double eblAngle = 45d;                              //EBL的默认方位
        private double vrmNuaticalMile = 0;                         //VRM的地理距离
        private int vrmRange = 100;                                 //VRM的默认屏幕距离, 100个像素
        private bool showEBLVRM = false;                            //是否绘制EBL/VRM
        private bool canEblVrmCenterMove = false;                   //是否移动中心点
        private bool canEblMove = false;                            //是否移动EBL
        private bool canVrmMove = false;                            //是否移动VRM
        private SKColor eblVrmColor = new SKColor(230, 121, 56);    //EBL/VRM的颜色
        private SKPaint eblVrmBrush = null;                         //用于绘制中心点
        private SKPaint eblVrmPen = null;                           //用于绘制线
        private readonly float[] LongDash = new float[] { 20, 4 };  //虚线类型
    
  2. 为窗体添加菜单控件MenuStrip,并为其添加菜单项ToolStripMenuItem,命名为eblVrmToolStripMenuItem。在状态栏添加ToolStripStatusLabel,命名为eblVrmInfo,用于显示EBL/VRM信息。
  3. eblVrmToolStripMenuItem绑定单击事件eblVrmToolStripMenuItem_Click
        private void eblVrmToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (eblVrmBrush == null) //画刷初始化
            {
                eblVrmBrush = new SKPaint() { Color = eblVrmColor, Style = SKPaintStyle.Fill};
                eblVrmPen = new SKPaint() { Color = eblVrmColor, StrokeWidth = 2,
                    PathEffect = SKPathEffect.CreateDash(LongDash, 0), Style = SKPaintStyle.Stroke};
            }
    
            showEBLVRM = !showEBLVRM;
            if (showEBLVRM)
            {
                eblVrmCenterPos = GeoTools.ScreenPointToGeoPosition(
                    current_Width / 2, current_Width / 2, current_Scale, current_Dx, current_Dy);
                eBLVRMOtherPos = null;
            }
            else
            {
                canEblVrmCenterMove = false;
                canEblMove = false;
                canVrmMove = false;
                eblAngle = 45d;
                vrmRange = 100;
                this.eblVrmInfo.Text = "";
            }
    
            showEBLVRMInfo();
    
            this.skiaView.Refresh();
        }
    
        private void showEBLVRMInfo()
        {
            if (showEBLVRM) this.eblVrmInfo.Text = $"VRM: {vrmNuaticalMile:0.0}'  EBL: {eblAngle:0.0}°";
            else this.eblVrmInfo.Text = "";
        }
    
  4. 调整skiaView_PaintSurface代码,在绘制经纬线及经纬度代码后,添加如下代码:
            //绘制EBL/VRM
            if (showEBLVRM) DrawEBLVRM(canvas);
    
  5. 编写方法DrawEBLVRM
        private void DrawEBLVRM(SKCanvas ca)
        {
            //绘制中心点,半径为4像素
            eblVrmCenterPoint = GeoTools.GeoPositionToScreenPoint(eblVrmCenterPos, current_Scale, current_Dx, current_Dy);
            ca.DrawCircle(eblVrmCenterPoint.X, eblVrmCenterPoint.Y, 4, eblVrmBrush);
    
            //绘制线
            //以中心线,方位角做射线,判断方位线与视窗边缘的交点
            eblVrmOtherPoint = GeoTools.FindIntersectPoint(eblVrmCenterPoint, eblAngle, current_Width, current_Height);
            ca.DrawLine(eblVrmCenterPoint.X, eblVrmCenterPoint.Y, eblVrmOtherPoint.X, eblVrmOtherPoint.Y, eblVrmPen);
    
            //绘制圆
            if (eblVrmOtherPos == null)
            {
                eblVrmOtherPos = GeoTools.ScreenPointToGeoPosition(eblVrmCenterPoint.X + vrmRange, 
                    eblVrmCenterPoint.Y, current_Scale, current_Dx, current_Dy);
            }
            else
            {
                var pt = GeoTools.GeoPositionToScreenPoint(eblVrmOtherPos, current_Scale, current_Dx, current_Dy);
                vrmRange = (int)Math.Sqrt(GeoTools.DistanceSqrd(pt, eblVrmCenterPoint));
            }
            vrmNuaticalMile = GeoTools.Distance(eblVrmCenterPos, eblVrmOtherPos);
            ca.DrawCircle(eblVrmCenterPoint.X, eblVrmCenterPoint.Y, vrmRange, eblVrmPen);
    
            showEBLVRMInfo();
        }
    
  6. 在显示EBL/VRM时,涉及到鼠标操作。
    • 按下鼠标,需要判断鼠标位置是否在EBL/VRM附近。在附近,则移动EBL/VRM,否则移动海图。
      private void skiaView_MouseDown(object sender, MouseEventArgs e)
      {
          if (e.Button == MouseButtons.Left)
          {
              //是否绘制VRM
              if (showEBLVRM)
              {
                  if (GeoTools.DistanceSqrd(e.Location, eblVrmCenterPoint) <= GeoTools.DisTorelenceSqrd)
                  {
                      canEblVrmCenterMove = true;  //中心点移动
                  }
                  else
                  {
                      var disToCenter = Math.Sqrt(GeoTools.DistanceSqrd(e.Location, eblVrmCenterPoint));
                      if (Math.Abs(disToCenter - vrmRange) <= GeoTools.DisTorelence)
                      {
                          canVrmMove = true; //VRM移动
                      }
      
                      var disToEBL = GeoTools.DistanceFromLineSqrd(e.Location, eblVrmCenterPoint, eblVrmOtherPoint);
                      if (disToEBL <= GeoTools.DisTorelenceSqrd)
                      {
                          canEblMove = true; //EBL移动
                      }
                  }
              }
      
              if (!canEblMove && !canVrmMove && !canEblVrmCenterMove)
              {
                  //开始平移,并记录鼠标位置
                  this.Cursor = Cursors.Hand;
                  isDragging = true;
                  preMousePosX = e.X;
                  preMousePosY = e.Y;
      
                  //此处无法直接获取截图,因此设为空
                  screenImage = null;
              }
          }
      }
      
    • 非平移模式下,如果需要移动EBL/VRM,则将EBL/VRM移到鼠标位置处。
      private void skiaView_MouseMove(object sender, MouseEventArgs e)
      {
          if (isDragging)
          {
              //按住鼠标移动,记录截图平移量
              screenOffsetX = e.X - preMousePosX;
              screenOffsetY = e.Y - preMousePosY;
      
              //只是移动截图
              this.skiaView.Refresh();
          }
          else //非平移海图模式
          {
              if (showEBLVRM) //EBL/VRM显示
              {
                  var needRefresh = false;
                  if (canEblVrmCenterMove)
                  {
                      //计算新的中心点
                      eblVrmCenterPoint = e.Location;
                      eblVrmCenterPos = GeoTools.ScreenPointToGeoPosition(e.Location.X, e.Location.Y, 
                          current_Scale, current_Dx, current_Dy);
                      eblVrmOtherPos = null;
                      needRefresh = true;
                  }
      
                  if (canVrmMove)
                  {
                      //计算新距标圈的某一位置
                      eblVrmOtherPos = GeoTools.ScreenPointToGeoPosition(e.Location.X, e.Location.Y,
                          current_Scale, current_Dx, current_Dy);
                      needRefresh = true;
                  }
      
                  if (canEblMove)
                  {
                      //计算新方位
                      double ang = GeoTools.AngleBetweenTwoPoints(e.X, e.Y, eblVrmCenterPoint.X, eblVrmCenterPoint.Y);
      
                      if (Math.Abs(ang - eblAngle) > 0.1)
                      {
                          eblAngle = ang;
                          needRefresh = true;
                      }
                  }
      
                  if (needRefresh) this.skiaView.Refresh();
              }
          }
      
          var pos = GeoTools.ScreenPointToGeoPosition(e.X, e.Y, current_Scale, current_Dx, current_Dy);
          mouseInfo.Text = $"屏幕坐标: {e.X} {e.Y}  地理坐标: {pos}";
      }
      
    • 松开鼠标时,重置EBL/VRM的可移动状态。
      private void skiaView_MouseUp(object sender, MouseEventArgs e)
      {
          if (e.Button == MouseButtons.Left)
          {
              if (showEBLVRM)
              {
                  canVrmMove = false;
                  canEblMove = false;
                  canEblVrmCenterMove = false;
              }
      
              if (isDragging)
              {
                  this.Cursor = Cursors.Default;
                  isDragging = false;
                  current_Dx += (e.X - preMousePosX);
                  current_Dy -= (e.Y - preMousePosY);
      
                  //重绘海图
                  this.skiaView.Refresh();
              }
          }
      }
      
  7. 最终效果如下图所示:
EBL/VRM示意图

你可能感兴趣的:(2.2.6 电子海图系统解析及开发 海图显示 - 绘制EBL/VRM)