flyweight模式和图元几何变换

简介: 在flyweight模式,指的是具有大量的轻量级对象,我们为这些对象建立一个实体对象,其他则为“虚像”或者称为对该实体的一种“引用”。在我从前的项目中,电力系统的矢量图中,有大量设备,同种类型设备采用一种符号绘制,称为图元。
  在flyweight模式,指的是具有大量的轻量级对象,我们为这些对象建立一个实体对象,其他则为“虚像”或者称为对该实体的一种“引用”。在我从前的项目中,电力系统的矢量图中,有大量设备,同种类型设备采用一种符号绘制,称为图元。这里就属于一中flyweight模式应用。例如下图:

        

          可以看到上图中的图元,例如开关,变压器,杆塔,电站等。这里我们为图中使用到的图元建立一个图元库,把图元存放到图元库中。在地图上显示的所有图元都是对图元库中的对象的一个引用。而在地图上的地图对象可以通过对图元的几何变换得到,即旋转,缩放,平移。

          控件内部数据组织如下:

          控件

             |--------图元库(图元集合)

             |--------图层和地图对象集合

            因此我们定义一个变换参数结构如下:

public struct TransParams
 {

            public int angle;         //旋转角度
            public double scale;  //缩放因子
            public double dx;      //水平偏移
            public double dy;      //垂直偏移

} 

我们可以看到一个图元有自己的坐标系统,它在自身坐标系统中的坐标需要经过变换,才能得到最终的绘制坐标。

对于平移:

          x=x+dx,

按原点缩放:

          x=x*scale;

旋转,需要使用扩展到三维的矩阵方程:

(1)二维坐标变换的扩维矩阵方程为:

                               [  m11     m12     0 ]
   [x' y' 1]=[x y 1]   [  m21     m22     0 ]
                               [   dx        dy      1 ]
   即
          x' = m11*x + m21* y + dx
          y' = m12*x + m22*y + dy


 (2)后偏移法的变换矩阵:
       x' = x * scale + dx
       y' = y * scale + dy

  
        [ scale       0      0 ]
        [   0        scale   0 ]
        [   dx       dy      1 ]

(3)围绕原点(0,0)(向y轴正向)旋转a角度的变换矩阵:
        x' = x * cos(a) - y * sin(a)
        y' = x * sin(a) + y * cos(a)

         [  cos(a)   sin(a)   0 ]
         [ -sin(a)   cos(a)   0 ]
         [     0            0        1 ] 

 (4)先围绕原点正向旋转a度,再缩放z倍,再平移dx,dy的变换矩阵:
      x' = x *cos(a)*z -y *sin(a)*z +dx
      y' = x *sin(a)*z +y *cos(a)*z +dy

     [  cos(a)*z   sin(a)*z   0 ]
     [ -sin(a)*z   cos(a)*z   0 ]
     [     dx        dy       1 ]

(5)上式的逆变换是:
         x = x' *  cos(a)/z +y *sin(a)/z -(dx*cos(a)+dy*sin(a))/z
         y = x' * -sin(a)/z +y *cos(a)/z +(dx*sin(a)-dy*cos(a))/z
 
         [       cos(a)                                   -sin(a)               0 ]
         [       sin(a)                                     cos(a)              0 ] / z
         [ -dx*cos(a)-dy*sin(a)       dx*sin(a)-dy*cos(a)     1 ] 

因此我们可以给出正变换和逆变换的代码:

//  正变换:<returns>返回变换后的坐标数组</returns>
public   static   double [] Transform( double  x, double  y,TransParams tran)
... {
    
//换成弧度
    double a=tran.angle*MapHelper.Factor_DegreeToRadian;
    
double sina=Math.Sin(a);
    
double cosa=Math.Cos(a);
    
double[] result=new double[2];
    result[
0]=x* cosa *tran.scale - y* sina *tran.scale + tran.dx;
    result[
1]=x* sina *tran.scale + y* cosa *tran.scale + tran.dy;
    
return result;
}


// 逆变换:
public   static   double [] Detransform( double  x, double  y,TransParams tran)
... {
    
//注意z不可以为0!
    if(tran.scale==0)
        
return new double[]...{0,0};
    
//换成弧度
    double a=tran.angle*MapHelper.Factor_DegreeToRadian;
    
double sina=Math.Sin(a);
    
double cosa=Math.Cos(a);
    
double[] result=new double[2];
    result[
0]=(x*cosa + y*sina -tran.dx*cosa-tran.dy*sina)/tran.scale;
    result[
1]=(x*(-sina) + y*cosa + tran.dx*sina-tran.dy*cosa)/tran.scale;
    
return result;            
}

 最后,我们为何要使用逆变换?这是因为地图对象实际上在该位置并不存在,当鼠标点击时,只有具体的地图对象才能够做点击测试的判断,而对引用对象我们无法根据引用对象本身去评判是否选中。因此这时我们依然需要委托被引用对象去判断。这时,我们需要把实际的鼠标坐标点,采用一次和实际对象中的变换参数的逆变换,即把鼠标坐标变换到了实际图元坐标系统中,这时我们就可以用变换到图元坐标系统内的点去完成鼠标点击测试了。如下图所示:
        

        例如如下的代码用于判断鼠标按下时,对象是否被鼠标捕获:这里,,(x,y)是鼠标点在客户区内坐标,(Cx,Cy)是原始地图坐标。当鼠标按下时,我们首先从系统获知的是客户区坐标(x,y),然后我们根据当前视图参数(MapView,包含缩放倍数和起点坐标),从客户区坐标变换到原始地图坐标(Cx,Cy),然后把这两个坐标数据下发到各地图对象去尝试捕获。下面的代码给出了一个链接对象的捕获方式,首先把原始地图坐标反变换到被链接对象(m_linker)坐标系统内,然后调用m_linker的相应函数去判断。

public   override   bool  BeCaptured( int  x,  int  y,  double  Cx,  double  Cy,  double  error,MapView view)
... {
    
if(this.m_linker==null)
        
return false;
    
//先将坐标点反变换!
    double[] p=MapHelper.Detransform(Cx,Cy,this.m_tran);
    
int x1=LineViewCtl.CXToX(p[0],view.OffsetX,view.Zoom);
    
int y1=LineViewCtl.CYToY(p[1],view.OffsetY,view.Zoom);
    
return this.m_linker.BeCaptured(x1,y1,p[0],p[1],error,view);
}

目录
相关文章
|
Linux
Linux系统之touch命令的基本使用
Linux系统之touch命令的基本使用
357 1
|
7月前
|
机器学习/深度学习 人工智能 监控
鸿蒙赋能智慧物流:AI类目标签技术深度解析与实践
在数字化浪潮下,物流行业面临变革,传统模式的局限性凸显。AI技术为物流转型升级注入动力。本文聚焦HarmonyOS NEXT API 12及以上版本,探讨如何利用AI类目标签技术提升智慧物流效率、准确性和成本控制。通过高效数据处理、实时监控和动态调整,AI技术显著优于传统方式。鸿蒙系统的分布式软总线技术和隐私保护机制为智慧物流提供了坚实基础。从仓储管理到运输监控再到配送优化,AI类目标签技术助力物流全流程智能化,提高客户满意度并降低成本。开发者可借助深度学习框架和鸿蒙系统特性,开发创新应用,推动物流行业智能化升级。
215 1
|
存储 负载均衡 监控
HBase分布式数据库架构及原理
Client是操作HBase集群的入口,对于管理类的操作,如表的增、删、改操纵,Client通过RPC与HMaster通信完成,对于表数据的读写操作,Client通过RPC与RegionServer交互,读写数据。
949 0
HBase分布式数据库架构及原理
|
消息中间件 运维 Java
支付系统的心脏:简洁而精妙的状态机设计与核心代码实现
本篇主要讲清楚什么是状态机,简洁的状态机对支付系统的重要性,状态机设计常见误区,以及如何设计出简洁而精妙的状态机,核心的状态机代码实现等。 我前段时间面试一个工作过4年的同学竟然没有听过状态机。假如你没有听过状态机,或者你听过但没有写过,或者你是使用if else 或switch case来写状态机的代码实现,建议花点时间看看,一定会有不一样的收获。
|
监控 算法 Java
深入理解Java虚拟机:JVM调优的实用策略
在Java应用开发中,性能优化常常成为提升系统响应速度和处理能力的关键。本文将探讨Java虚拟机(JVM)调优的核心概念,包括垃圾回收、内存管理和编译器优化等方面,并提供一系列经过验证的调优技巧。通过这些实践指导,开发人员可以有效减少延迟,提高吞吐量,确保应用稳定运行。 【7月更文挑战第16天】
191 4
|
JavaScript API
node.js之模块系统
node.js之模块系统
|
存储 Java 大数据
分布式数据库HBase的安装部署和环境搭建的集群模式
HBase是一个分布式数据库系统,能够支持高性能、高可靠性、高伸缩性的数据存储和读写操作。在大数据时代,HBase成为了一个越来越受欢迎的数据库选择。本文将介绍HBase的集群模式的安装部署和环境搭建,帮助开发者快速上手。
1574 2
|
前端开发 Java 数据库
SpringMVC的架构有什么优势?——表单和数据校验(四)
SpringMVC的架构有什么优势?——表单和数据校验(四)
|
存储 前端开发 数据安全/隐私保护
十五.SpringCloud+Security+Oauth2实现微服务授权 -前端登录实战
SpringCloud+Security+Oauth2实现微服务授权 -前端登录实战