EBL/VRM是电子海图系统很常见的功能。至少应具有如下功能:
- 显示EBL/VRM;
- 如果存在当前船位,EBL/VRM的初始中心是船位,否则是屏幕中心;
- 按住鼠标可拖拽调整EBL的方位;
- 按住鼠标可拖拽调整VRM的大小;
- 按住鼠标可拖拽调整EBL/VRM中心的位置,
- 与鼠标平移、缩放操作不冲突。
判断点或线是否移动,都需要计算鼠标位置与点、线之间的距离。当距离小于一定范围(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);
}
- 窗体添加如下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 }; //虚线类型
- 为窗体添加菜单控件
MenuStrip
,并为其添加菜单项ToolStripMenuItem
,命名为eblVrmToolStripMenuItem
。在状态栏添加ToolStripStatusLabel
,命名为eblVrmInfo
,用于显示EBL/VRM信息。 - 为
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 = ""; }
- 调整
skiaView_PaintSurface
代码,在绘制经纬线及经纬度代码后,添加如下代码://绘制EBL/VRM if (showEBLVRM) DrawEBLVRM(canvas);
- 编写方法
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(); }
- 在显示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(); } } }
- 按下鼠标,需要判断鼠标位置是否在EBL/VRM附近。在附近,则移动EBL/VRM,否则移动海图。
- 最终效果如下图所示:
EBL/VRM示意图