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

  • 相关阅读:
    Atitit topic index Abt 150 toic [原]Atitit hi dev eff topic by use dsl sql coll op 提升开发效率sql ds
    Atitit xml转json总结 目录 1.1. XML和JSON之间没有直接映射;元素类型问题 1 1.2. Xml与json的对应关系 2 1.3. 范例 2 2. Jsonlib的问题,,不
    Atitit stomp.js conn连接activemq 目录 1.1. activemq 启动,已经默认开启了stomp ws的接口。。地址是 1 1.2. Js 客户端代码 1 1.3
    Atitit 业务领域体系分类 目录 1. 按照互联网企业类型以及只是体系类的分类 2 2. 电子商务 2 3. **通信类社交 Im类 em 2 4. **信息搜索类爬虫 2 4.1. 媒体
    atitit software sys 软件技术领域工业体系.docx 目录 1. 技术领域一级大类10大类 2 2. 理论与软件设计方法学 2 2.1. 计算机原理 计算机科学导论 2 2.2.
    Atitit api design Usability simple 易用性之简单化设计 目录 1. 理论原则 2 1.1. 概念简单 2 1.2. 切换到了“write less, do more
    Atitit 远程工作的几种办公模式 目录 1. 未来的趋势 远程办公 1 1.1. 遥远的阴影 1 1.2. 一个单中心的团队,是一个团队,每个人都被共处于同一物理位置。 2 1.3. 一个多站
    Atitit 保证产品易用性的方法总结 目录 1. 什么是易用性 易学 易见 三角关系 1 2. 易用性原理 三原则 易见 映射 反馈 2 2.1. 易见 Visibility 可读性 2 2.2.
    Atitit 高级人员要看哪些源码 目录 1. Ati看过的源码 1 1.1. Ui类 1 1.2. Mvc类 1 1.3. 数据库类 1 1.4. 算法类 1 2. 看源码的意义 2 2.1. 一
    Atitit 初级 中级 高级 软件工程师的区别 非功能性需求 目录 1. 初级 业务功能 1 1.1. 中级 独立完成业务功能 已经非常见api功能 更加广阔 1 2. 高级 非功能性需求
  • 原文地址:https://www.cnblogs.com/yhlx125/p/3498548.html
Copyright © 2020-2023  润新知