[SharpMap] 屏幕坐标和Map坐标转换

 1. SharpMap中屏幕坐标和地图Map坐标转换:

 1 using System.Drawing;
 2 using GeoAPI.Geometries;
 3 
 4 namespace SharpMap.Utilities
 5 {
 6     /// <summary>
 7     /// Class for transforming between world and image coordinate
 8     /// </summary>
 9     public class Transform
10     {
11         /// <summary>
12         /// Transforms from world coordinate system (WCS) to image coordinates
13         /// 将世界坐标转换为image坐标
14         /// NOTE: This method DOES NOT take the MapTransform property into account (use <see cref="Map.WorldToImage(GeoAPI.Geometries.Coordinate,bool)"/> instead)
15         /// </summary>
16         /// <param name="p">Point in WCS</param>
17         /// <param name="map">Map reference</param>
18         /// <returns>Point in image coordinates</returns>
19         public static PointF WorldtoMap(Coordinate p, Map map)
20         {
21             //if (map.MapTransform != null && !map.MapTransform.IsIdentity)
22             //    map.MapTransform.TransformPoints(new System.Drawing.PointF[] { p });
23             if (p.IsEmpty())
24                 return PointF.Empty;
25 
26             var result = new PointF();
27 
28             var height = (map.Zoom * map.Size.Height) / map.Size.Width;
29             var left = map.Center.X - map.Zoom * 0.5;
30             var top = map.Center.Y + height * 0.5 * map.PixelAspectRatio;
31             result.X = (float)((p.X - left) / map.PixelWidth);
32             result.Y = (float)((top - p.Y) / map.PixelHeight);
33             if (double.IsNaN(result.X) || double.IsNaN(result.Y))
34                 result = PointF.Empty;
35             return result;
36         }
37 
38         /// <summary>
39         /// Transforms from image coordinates to world coordinate system (WCS).
40         /// NOTE: This method DOES NOT take the MapTransform property into account (use <see cref="Map.ImageToWorld(System.Drawing.PointF,bool)"/> instead)
41         /// </summary>
42         /// <param name="p">Point in image coordinate system</param>
43         /// <param name="map">Map reference</param>
44         /// <returns>Point in WCS</returns>
45         public static Coordinate MapToWorld(PointF p, Map map)
46         {
47             if (map.Center.IsEmpty() || double.IsNaN(map.MapHeight))
48             {
49                 return new Coordinate(0, 0);
50             }
51             var ul = new Coordinate(map.Center.X - map.Zoom * .5, map.Center.Y + map.MapHeight * .5);
52             return new Coordinate(ul.X + p.X * map.PixelWidth,
53                                   ul.Y - p.Y * map.PixelHeight);
54         }
55     }
56 }
Transform

详细分析: http://www.cnblogs.com/yhlx125/archive/2012/02/10/2342282.html

2. OpenS-CAD中的实现

已知屏幕分辨率每英寸像素点数,一般为96dpi,  定义float m_screenResolution = 96;

1. 初始化CanvasCtrl时,首先调用OnResize()方法。

 1 protected override void OnResize(EventArgs e)
 2         {
 3             base.OnResize(e);
 4 
 5             if (m_lastCenterPoint != UnitPoint.Empty && Width != 0)
 6                 SetCenterScreen(ToScreen(m_lastCenterPoint), false);
 7             m_lastCenterPoint = CenterPointUnit();
 8             m_staticImage = null;
 9             DoInvalidate(true);
10         }
OnResize

由于m_lastCenterPoint是结构体变量,所以首先设置到中心点m_lastCenterPoint,即(0,0),是Unit坐标

if (m_lastCenterPoint != UnitPoint.Empty && Width != 0)
    SetCenterScreen(ToScreen(m_lastCenterPoint), false);

接着调用m_lastCenterPoint = CenterPointUnit();

通过直角坐标的左上角点和右下角点计算。其实初始化时候执行该方法没有起到设置基准点的作用。可以跳过这2次,等窗体Resize的时候再看。

 1 public UnitPoint CenterPointUnit()
 2         {
 3             UnitPoint p1 = ScreenTopLeftToUnitPoint();
 4             UnitPoint p2 = ScreenBottomRightToUnitPoint();
 5             UnitPoint center = new UnitPoint();
 6             center.X = (p1.X + p2.X) / 2;
 7             center.Y = (p1.Y + p2.Y) / 2;
 8             return center;
 9         }
10         public UnitPoint ScreenTopLeftToUnitPoint()
11         {
12             return ToUnit(new PointF(0, 0));
13         }
14         public UnitPoint ScreenBottomRightToUnitPoint()
15         {
16             return ToUnit(new PointF(this.ClientRectangle.Width, this.ClientRectangle.Height));
17         }
CenterPointUnit

2. 接着打开文档DocumentForm,在构造的过程中设置文档画布的视点中心坐标为(0,0)。这样就实现了绘图画布原点坐标和屏幕(客户区)中心点的对应,形成基准点。默认的数据的长度单位为inch,屏幕坐标的单位为像素。这两者之间存在比例关系,通过Zoom缩放比例来实现尺度的变换,同时结合平移量和偏移距离计算出鼠标点的世界坐标。

m_canvas.SetCenter(new UnitPoint(0, 0));

或者加载完数据,设置画布的视点中心坐标。

m_canvas.SetCenter(m_data.CenterPoint);

 1 public DocumentForm(string filename)
 2         {
 3             InitializeComponent();
 4 
 5             Text = "<New Document>";
 6             m_data = new DataModel();
 7             if (filename.Length > 0 && File.Exists(filename) && m_data.Load(filename))
 8             {
 9                 Text = filename;
10                 m_filename = filename;
11             }
12 
13             m_canvas = new CanvasCtrl(this, m_data);
14             m_canvas.Dock = DockStyle.Fill;
15             Controls.Add(m_canvas);
16             m_canvas.SetCenter(new UnitPoint(0, 0));
17             m_canvas.RunningSnaps = new Type[] 
18                 {
19                 typeof(VertextSnapPoint),
20                 typeof(MidpointSnapPoint),
21                 typeof(IntersectSnapPoint),
22                 typeof(QuadrantSnapPoint),
23                 typeof(CenterSnapPoint),
24                 typeof(DivisionSnapPoint),
25                 };
26 
27             m_canvas.AddQuickSnapType(Keys.N, typeof(NearestSnapPoint));
28             m_canvas.AddQuickSnapType(Keys.M, typeof(MidpointSnapPoint));
29             m_canvas.AddQuickSnapType(Keys.I, typeof(IntersectSnapPoint));
30             m_canvas.AddQuickSnapType(Keys.V, typeof(VertextSnapPoint));
31             m_canvas.AddQuickSnapType(Keys.P, typeof(PerpendicularSnapPoint));
32             m_canvas.AddQuickSnapType(Keys.Q, typeof(QuadrantSnapPoint));
33             m_canvas.AddQuickSnapType(Keys.C, typeof(CenterSnapPoint));
34             m_canvas.AddQuickSnapType(Keys.T, typeof(TangentSnapPoint));
35             m_canvas.AddQuickSnapType(Keys.D, typeof(DivisionSnapPoint));
36 
37             m_canvas.KeyDown += new KeyEventHandler(OnCanvasKeyDown);
38             SetupMenuItems();
39             SetupDrawTools();
40             SetupLayerToolstrip();
41             SetupEditTools();
42             UpdateLayerUI();
43 
44             MenuStrip menuitem = new MenuStrip();//创建文档的主菜单
45             menuitem.Items.Add(m_menuItems.GetMenuStrip("edit"));
46             menuitem.Items.Add(m_menuItems.GetMenuStrip("draw"));
47             menuitem.Visible = false;
48             Controls.Add(menuitem);
49             this.MainMenuStrip = menuitem;
50         }
51         protected override void OnLoad(EventArgs e)
52         {
53             base.OnLoad(e);
54             m_canvas.SetCenter(m_data.CenterPoint);
55         }
View Code

  2.1查看SetCenter(UnitPoint unitPoint)方法,首先调用PointF point = ToScreen(unitPoint);将unitPoint转换PointF point.找到地图原点对应的屏幕坐标,应该是屏幕左下角点偏移(25,-25)。

 1         /// <summary>
 2         /// 设置画布到屏幕的中心
 3         /// </summary>
 4         /// <param name="rPoint">直角坐标系坐标</param>
 5         public void SetCenter(RPoint unitPoint)
 6         {
 7             //将unitPoint点对应到屏幕上point
 8             PointF point = Transform.ToScreen(unitPoint, this);
 9             m_lastCenterPoint = unitPoint;
10             //将unitPoint偏移到屏幕中心
11             SetCenterScreen(point, false);
12         }
SetCenter

  这里注意计算Unit坐标到屏幕坐标的ToScreen()方法中,transformedPoint.Y = ScreenHeight() - transformedPoint.Y;//将Unit坐标系转换为屏幕坐标系,Y轴反向

 其中ScreenHeight()方法似乎有点问题,修改后如下。

 1  /// <summary>
 2         /// 将Unit坐标转换到屏幕坐标
 3         /// </summary>
 4         /// <param name="point"></param>
 5         /// <returns></returns>
 6         public PointF ToScreen(UnitPoint point)
 7         {
 8             PointF transformedPoint = Translate(point);
 9             transformedPoint.Y = ScreenHeight() - transformedPoint.Y;//将Unit坐标系转换为屏幕坐标系,Y轴反向
10             transformedPoint.Y *= m_screenResolution * m_model.Zoom;
11             transformedPoint.X *= m_screenResolution * m_model.Zoom;
12 
13             transformedPoint.X += m_panOffset.X + m_dragOffset.X;
14             transformedPoint.Y += m_panOffset.Y + m_dragOffset.Y;
15             return transformedPoint;
16         }
ToScreen
1 float ScreenHeight()
2         {
3             return (float)(ToUnit(this.ClientRectangle.Height));
4             //return (float)(ToUnit(this.ClientRectangle.Height) / m_model.Zoom);
5         }
ScreenHeight

   这样就引出了ToUnit(float screenvalue)函数,将屏幕距离转换为英寸数。

1 public double ToUnit(float screenvalue)
2         {
3             return (double)screenvalue / (double)(m_screenResolution * m_model.Zoom);
4         }

3. 接着将unitPoint赋值给m_lastCenterPoint

m_lastCenterPoint = unitPoint;

SetCenterScreen(point, false);

调用了SetCenterScreen()方法,

 1 protected  void SetCenterScreen(PointF screenPoint, bool setCursor)
 2         {
 3             float centerX = ClientRectangle.Width / 2;
 4             m_panOffset.X += centerX - screenPoint.X;
 5             
 6             float centerY = ClientRectangle.Height / 2;
 7             m_panOffset.Y += centerY - screenPoint.Y;
 8 
 9             if (setCursor)
10                 Cursor.Position = this.PointToScreen(new Point((int)centerX, (int)centerY));
11             DoInvalidate(true);
12         }

4.理解了public PointF ToScreen(UnitPoint point),那public UnitPoint ToUnit(PointF screenpoint)也好理解了。

 1  /// <summary>
 2         ///  将屏幕坐标转换到Unit坐标
 3         /// </summary>
 4         /// <param name="screenpoint"></param>
 5         /// <returns></returns>
 6         public UnitPoint ToUnit(PointF screenpoint)
 7         {
 8             float panoffsetX = m_panOffset.X + m_dragOffset.X;
 9             float panoffsetY = m_panOffset.Y + m_dragOffset.Y;
10             float xpos = (screenpoint.X - panoffsetX) / (m_screenResolution * m_model.Zoom);
11             float ypos = ScreenHeight() - ((screenpoint.Y - panoffsetY)) / (m_screenResolution * m_model.Zoom);
12             return new UnitPoint(xpos, ypos);
13         }
ToUnit

5.最后单独说一下ToScreen(UnitPoint point)和ToUnit(PointF screenpoint)中的两个变量

PointF m_panOffset = new PointF(25, -25);
PointF m_dragOffset = new PointF(0, 0);

这里m_panOffset控制的是中心点Center的偏移量,是一个累计的量,相对于中心点。

m_dragOffset记录了每次移动过程中的移动量,每次产生一个新值。每次CanvasCtrl控件的OnMouseDown时累积到偏移量上,之后重新初始化,同时在OnMouseUp时的移动命令下重新初始化(如下代码),似乎重复了。

1 if (m_commandType == eCommandType.pan)
2             {
3                 m_panOffset.X += m_dragOffset.X;
4                 m_panOffset.Y += m_dragOffset.Y;
5                 m_dragOffset = new PointF(0, 0);
6             }

在protected override void OnMouseMove(MouseEventArgs e)事件中

1 if (m_commandType == eCommandType.pan && e.Button == MouseButtons.Left)
2             {
3                 m_dragOffset.X = -(m_mousedownPoint.X - e.X);
4                 m_dragOffset.Y = -(m_mousedownPoint.Y - e.Y);
5                 m_lastCenterPoint = CenterPointUnit();
6                 DoInvalidate(true);
7             }
View Code

可知,m_dragOffset和m_panOffset记录的是偏移的屏幕坐标。

原文地址:https://www.cnblogs.com/yhlx125/p/3498548.html