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 ]

 (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;            
}

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

例如如下的代码: 

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);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值