可以看到上图中的图元,例如开关,变压器,杆塔,电站等。这里我们为图中使用到的图元建立一个图元库,把图元存放到图元库中。在地图上显示的所有图元都是对图元库中的对象的一个引用。而在地图上的地图对象可以通过对图元的几何变换得到,即旋转,缩放,平移。
控件内部数据组织如下:
控件
|--------图元库(图元集合)
|--------图层和地图对象集合
因此我们定义一个变换参数结构如下:
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 ]
因此我们可以给出正变换和逆变换的代码:
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的相应函数去判断。
{
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);
}