2.2.6 電子海圖系統(tǒng)解析及開發(fā) 海圖顯示 - 繪制EBL/VRM

EBL/VRM是電子海圖系統(tǒng)很常見的功能容达。至少應(yīng)具有如下功能:

  1. 顯示EBL/VRM辆琅;
  2. 如果存在當(dāng)前船位,EBL/VRM的初始中心是船位感局,否則是屏幕中心胚想;
  3. 按住鼠標(biāo)可拖拽調(diào)整EBL的方位琐凭;
  4. 按住鼠標(biāo)可拖拽調(diào)整VRM的大小浊服;
  5. 按住鼠標(biāo)可拖拽調(diào)整EBL/VRM中心的位置统屈,
  6. 與鼠標(biāo)平移、縮放操作不沖突牙躺。

判斷點(diǎn)或線是否移動(dòng)愁憔,都需要計(jì)算鼠標(biāo)位置與點(diǎn)、線之間的距離孽拷。當(dāng)距離小于一定范圍(4像素)即可認(rèn)為需要移動(dòng)吨掌。繪制EBL/VRM過程中,涉及到的距離都是用的屏幕坐標(biāo)脓恕。其中計(jì)算點(diǎn)到直線的距離用到的公式為:

點(diǎn)到直接的距離公式

GeoTools里添加屏幕距離公式相關(guān)代碼(考慮到開方操作較為復(fù)雜膜宋,實(shí)際返回的為距離的平方數(shù))。

        //距離范圍及平方
        public const int DisTorelence = 4;
        public const int DisTorelenceSqrd = 4*4;

        //屏幕上兩點(diǎn)之間的距離的平方
        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);
        }

        //屏幕上點(diǎn)到直線的距離的平方炼幔,直線用兩坐標(biāo)點(diǎn)表示
        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相關(guān)字段:
        private S57Pos2D eblVrmCenterPos;                           //中心點(diǎn)地理坐標(biāo)
        private S57Pos2D eblVrmOtherPos;                            //VRM上一點(diǎn)的地理坐標(biāo)激蹲,計(jì)算距離用
        private Point eblVrmCenterPoint;                            //中心點(diǎn)屏幕坐標(biāo)
        private Point eblVrmOtherPoint;                             //VRM上一點(diǎn)的屏幕坐標(biāo),計(jì)算距離用
        private double eblAngle = 45d;                              //EBL的默認(rèn)方位
        private double vrmNuaticalMile = 0;                         //VRM的地理距離
        private int vrmRange = 100;                                 //VRM的默認(rèn)屏幕距離, 100個(gè)像素
        private bool showEBLVRM = false;                            //是否繪制EBL/VRM
        private bool canEblVrmCenterMove = false;                   //是否移動(dòng)中心點(diǎn)
        private bool canEblMove = false;                            //是否移動(dòng)EBL
        private bool canVrmMove = false;                            //是否移動(dòng)VRM
        private SKColor eblVrmColor = new SKColor(230, 121, 56);    //EBL/VRM的顏色
        private SKPaint eblVrmBrush = null;                         //用于繪制中心點(diǎn)
        private SKPaint eblVrmPen = null;                           //用于繪制線
        private readonly float[] LongDash = new float[] { 20, 4 };  //虛線類型
    
  2. 為窗體添加菜單控件MenuStrip江掩,并為其添加菜單項(xiàng)ToolStripMenuItem学辱,命名為eblVrmToolStripMenuItem乘瓤。在狀態(tài)欄添加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. 調(diào)整skiaView_PaintSurface代碼,在繪制經(jīng)緯線及經(jīng)緯度代碼后萨咕,添加如下代碼:
            //繪制EBL/VRM
            if (showEBLVRM) DrawEBLVRM(canvas);
    
  5. 編寫方法DrawEBLVRM统抬。
        private void DrawEBLVRM(SKCanvas ca)
        {
            //繪制中心點(diǎn),半徑為4像素
            eblVrmCenterPoint = GeoTools.GeoPositionToScreenPoint(eblVrmCenterPos, current_Scale, current_Dx, current_Dy);
            ca.DrawCircle(eblVrmCenterPoint.X, eblVrmCenterPoint.Y, 4, eblVrmBrush);
    
            //繪制線
            //以中心線危队,方位角做射線聪建,判斷方位線與視窗邊緣的交點(diǎn)
            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時(shí),涉及到鼠標(biāo)操作茫陆。
    • 按下鼠標(biāo)金麸,需要判斷鼠標(biāo)位置是否在EBL/VRM附近。在附近簿盅,則移動(dòng)EBL/VRM挥下,否則移動(dòng)海圖。
      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;  //中心點(diǎn)移動(dòng)
                  }
                  else
                  {
                      var disToCenter = Math.Sqrt(GeoTools.DistanceSqrd(e.Location, eblVrmCenterPoint));
                      if (Math.Abs(disToCenter - vrmRange) <= GeoTools.DisTorelence)
                      {
                          canVrmMove = true; //VRM移動(dòng)
                      }
      
                      var disToEBL = GeoTools.DistanceFromLineSqrd(e.Location, eblVrmCenterPoint, eblVrmOtherPoint);
                      if (disToEBL <= GeoTools.DisTorelenceSqrd)
                      {
                          canEblMove = true; //EBL移動(dòng)
                      }
                  }
              }
      
              if (!canEblMove && !canVrmMove && !canEblVrmCenterMove)
              {
                  //開始平移桨醋,并記錄鼠標(biāo)位置
                  this.Cursor = Cursors.Hand;
                  isDragging = true;
                  preMousePosX = e.X;
                  preMousePosY = e.Y;
      
                  //此處無法直接獲取截圖棚瘟,因此設(shè)為空
                  screenImage = null;
              }
          }
      }
      
    • 非平移模式下,如果需要移動(dòng)EBL/VRM喜最,則將EBL/VRM移到鼠標(biāo)位置處偎蘸。
      private void skiaView_MouseMove(object sender, MouseEventArgs e)
      {
          if (isDragging)
          {
              //按住鼠標(biāo)移動(dòng),記錄截圖平移量
              screenOffsetX = e.X - preMousePosX;
              screenOffsetY = e.Y - preMousePosY;
      
              //只是移動(dòng)截圖
              this.skiaView.Refresh();
          }
          else //非平移海圖模式
          {
              if (showEBLVRM) //EBL/VRM顯示
              {
                  var needRefresh = false;
                  if (canEblVrmCenterMove)
                  {
                      //計(jì)算新的中心點(diǎn)
                      eblVrmCenterPoint = e.Location;
                      eblVrmCenterPos = GeoTools.ScreenPointToGeoPosition(e.Location.X, e.Location.Y, 
                          current_Scale, current_Dx, current_Dy);
                      eblVrmOtherPos = null;
                      needRefresh = true;
                  }
      
                  if (canVrmMove)
                  {
                      //計(jì)算新距標(biāo)圈的某一位置
                      eblVrmOtherPos = GeoTools.ScreenPointToGeoPosition(e.Location.X, e.Location.Y,
                          current_Scale, current_Dx, current_Dy);
                      needRefresh = true;
                  }
      
                  if (canEblMove)
                  {
                      //計(jì)算新方位
                      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 = $"屏幕坐標(biāo): {e.X} {e.Y}  地理坐標(biāo): {pos}";
      }
      
    • 松開鼠標(biāo)時(shí)瞬内,重置EBL/VRM的可移動(dòng)狀態(tài)迷雪。
      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示意圖
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市遂鹊,隨后出現(xiàn)的幾起案子振乏,更是在濱河造成了極大的恐慌蔗包,老刑警劉巖秉扑,帶你破解...
    沈念sama閱讀 218,204評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異调限,居然都是意外死亡舟陆,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門耻矮,熙熙樓的掌柜王于貴愁眉苦臉地迎上來秦躯,“玉大人,你說我怎么就攤上這事裆装□獬校” “怎么了倡缠?”我有些...
    開封第一講書人閱讀 164,548評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長茎活。 經(jīng)常有香客問我昙沦,道長,這世上最難降的妖魔是什么载荔? 我笑而不...
    開封第一講書人閱讀 58,657評(píng)論 1 293
  • 正文 為了忘掉前任盾饮,我火速辦了婚禮,結(jié)果婚禮上懒熙,老公的妹妹穿的比我還像新娘丘损。我一直安慰自己,他們只是感情好工扎,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,689評(píng)論 6 392
  • 文/花漫 我一把揭開白布徘钥。 她就那樣靜靜地躺著,像睡著了一般定庵。 火紅的嫁衣襯著肌膚如雪吏饿。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,554評(píng)論 1 305
  • 那天蔬浙,我揣著相機(jī)與錄音猪落,去河邊找鬼。 笑死畴博,一個(gè)胖子當(dāng)著我的面吹牛笨忌,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播俱病,決...
    沈念sama閱讀 40,302評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼官疲,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了亮隙?” 一聲冷哼從身側(cè)響起途凫,我...
    開封第一講書人閱讀 39,216評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎溢吻,沒想到半個(gè)月后维费,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,661評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡促王,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,851評(píng)論 3 336
  • 正文 我和宋清朗相戀三年犀盟,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蝇狼。...
    茶點(diǎn)故事閱讀 39,977評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡阅畴,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出迅耘,到底是詐尸還是另有隱情贱枣,我是刑警寧澤监署,帶...
    沈念sama閱讀 35,697評(píng)論 5 347
  • 正文 年R本政府宣布,位于F島的核電站纽哥,受9級(jí)特大地震影響焦匈,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜昵仅,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,306評(píng)論 3 330
  • 文/蒙蒙 一缓熟、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧摔笤,春花似錦够滑、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至命辖,卻和暖如春况毅,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背尔艇。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評(píng)論 1 270
  • 我被黑心中介騙來泰國打工尔许, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人终娃。 一個(gè)月前我還...
    沈念sama閱讀 48,138評(píng)論 3 370
  • 正文 我出身青樓味廊,卻偏偏與公主長得像,于是被迫代替她去往敵國和親棠耕。 傳聞我的和親對(duì)象是個(gè)殘疾皇子余佛,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,927評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容