[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记录的是偏移的屏幕坐标。

你可能感兴趣的:(map)