手写地理信息组件系列 第4篇
地图的缩放变换
难度指数:★★☆☆☆
目录
前情回顾
前一篇,我们讨论了地图中,关于地图坐标与屏幕坐标间的互转内容。我们知道了地图显示窗口中,地物的位置和范围是由其Extent决定的,而这一篇将会动态的计算Extent,以实现地图的缩放和平移变换,并在上一篇形成的程序之上加入地图缩放变换能力,进一步加强地图组件的功能!
地图缩放变换的实质
电子地图区别于传统纸质地图的重要表现,在于其自由的缩放和平移。在地图组件中,缩放和平移都是通过调节地图窗口的地理范围得到。这里将地图的缩放和平移分别进行分析:
地图平移:
地图左右平移的实质是x坐标的增减,不涉及y坐标的变化。所以只需图中的minX、maxX地图坐标,在x轴上进行一次统一的加减操作。对象向右移动,两x坐标加正数。同理,对象向左移动,x加负数。但是加多少呢,不能简单地固定为一个数值,需要结合当前Extent的宽度确定(如果固定为一个数值会是什么效果?)。
具体做法应是,获取当前Extent的宽度乘以一个比率得到,这个比率一般设为一个小于1的浮点数。如0.2。向右平移的计算方式举例:newMinX = minX + width * 0.2、newMaxX = maxX + width * 0.2。上下平移的计算方式与此基本一致。
地图缩放:
地图缩放的过程,就是一个重新计算当前Extent角点地图坐标的过程。
图上,矩形代表视图的地理范围,放大(ZoomIn)即是由图中的x3-4,y3-4坐标位置变换为x1-2,y3-4;缩小(ZoomOut)就是放大操作的反向坐标变换。由于地图缩放的基点是不变的,都是视图的中心点,所以满足以下等式。
其中,系数T代表放大缩小的比率,其数值大于1。按照以当前范围求缩放范围、消去未知变量的思路,手动推导以上等式。
按照以上的推理,分别分析放大和缩小的数学关系:
放大计算
地图放大,可视当前视图地理范围(外部大矩形)的坐标为(x1,y1)、(x2,y2),利用缩放比率T计算(x3,y3)、(x4,y4),使大矩形的原坐标变为(x3,y3)、(x4,y4),达到地图放大的效果。
按照上边的手写推导过程,可推出下面等式。
缩小计算
同理,可以视当前视图地理范围(内部小矩形)的坐标为(x3,y3)、(x4,y4),利用缩放比率T计算(x1,y1)、(x2,y2),然后使小矩形的原坐标变为(x1,y1)、(x2,y2)。
亦可推导出下面等式。
地图Extent类的增强
在之前的设计中,Extent类一直是一个透明般存在,今天是时候拿Extent开刀了!
上文中已经有了多次强调,地图的缩放变换实质是一个动态的计算Extent的过程,所以把计算过程设计了在Extent类当中。
首先的,需要定义几种地图操作的枚举类型,枚举命名为MapAction
public enum MapAction
{
ZoomIn = 0, ZoomOut = 1,
MoveUp = 2, MoveDown = 3,
MoveLeft = 4, MoveRight = 5
}
至于每种枚举类型为什么要跟一个int数值与之匹配,这并非一个必需之举,但这是一个良好的习惯,它可以方便的让你在整数类型和枚举类型之间进行互转,比较有用。
实现的重点
现在Extent类中定义一个缩放变换的方法UpdateExtent,参数就是枚举MapAction,这样每当外部调用一次UpdateExtent,并传递一个地图操作参数MapAction,Extent将会被重新计算。
public class Extent
{
//左下
Vertex bottomLeft;
//右上
Vertex upRight;
public double MinX
{
get { return bottomLeft.x; }
}
public double MinY
{
get { return bottomLeft.y; }
}
public double MaxX
{
get { return upRight.x; }
}
public double MaxY
{
get { return upRight.y; }
}
public double Width
{
get { return upRight.x - bottomLeft.x; }
}
public double Height
{
get { return upRight.y - bottomLeft.y; }
}
public Extent(Vertex bottomleft, Vertex upright)
{
this.bottomLeft = bottomleft;
this.upRight = upright;
}
//缩放比率
double zoomRatio = 1.2;
//平移比率
double moveRatio = 0.1;
public void UpdateExtent(MapAction action)
{
double newMinX = bottomLeft.x, newMaxX = upRight.x,
newMinY = bottomLeft.y, newMaxY = upRight.y;
switch (action)
{
//放大
case MapAction.ZoomIn:
newMinX = (MinX + MaxX - Width / zoomRatio) / 2;
newMinY = (MinY + MaxY - Height / zoomRatio) / 2;
newMaxX = (MinX + MaxX + Width / zoomRatio) / 2;
newMaxY = (MinY + MaxY + Height / zoomRatio) / 2;
break;
//缩小
case MapAction.ZoomOut:
newMinX = (MinX + MaxX - Width * zoomRatio) / 2;
newMinY = (MinY + MaxY - Height * zoomRatio) / 2;
newMaxX = (MinX + MaxX + Width * zoomRatio) / 2;
newMaxY = (MinY + MaxY + Height * zoomRatio) / 2;
break;
//左移
case MapAction.MoveLeft:
newMinX = MinX + Width * moveRatio;
newMaxX = MaxX + Width * moveRatio;
break;
//右移
case MapAction.MoveRight:
newMinX = MinX - Width * moveRatio;
newMaxX = MaxX - Width * moveRatio;
break;
//上移
case MapAction.MoveUp:
newMinY = MinY + Height * moveRatio;
newMaxY = MaxY + Height * moveRatio;
break;
//下移
case MapAction.MoveDown:
newMinY = MinY - Height * moveRatio;
newMaxY = MaxY - Height * moveRatio;
break;
}
//重新设置视图地理范围
bottomLeft.x = newMinX;
bottomLeft.y = newMinY;
upRight.x = newMaxX;
upRight.y = newMaxY;
}
}
这里便应用了以上推导出的变换公式,如果你对Extent类的定义过程还不熟悉,可以点击链接,查看其定义过程。
实现地图缩放变换的过程
在之前的地图Form之上,又加入了一个类似于诺基亚方向键盘的“地图操控”分组框。里面定义了上边介绍的6种操作类型按钮。根据其功能定义,可以发现,这6个按钮的行为都是相似的,都是向Extent类的UpdateExtent函数传递一个MapAction参数。
为了消灭冗余代码,在Form1类中定义了一个MapButton_Click方法,统一handle这六个按钮的点击事件,作统一处理。
//缩放
private void MapButton_Click(object sender, EventArgs e)
{
SimpleButton btn = (SimpleButton)sender;
MapAction action = MapAction.ZoomIn;
if (btn == btn_MoveIn) action = MapAction.ZoomIn;
else if (btn == btn_ZoomOut) action = MapAction.ZoomOut;
else if (btn == btn_MoveLeft) action = MapAction.MoveLeft;
else if (btn == btn_MoveRight) action = MapAction.MoveRight;
else if (btn == btn_MoveUp) action = MapAction.MoveUp;
else if (btn == btn_MoveDown) action = MapAction.MoveDown;
mapExtent.UpdateExtent(action);
this.mapUpdate();
}
//更新界面--重绘
private void mapUpdate()
{
map.Update(mapExtent, this.ClientRectangle);
Graphics graphic = this.CreateGraphics();
//清理界面,准备重绘
graphic.Clear(this.BackColor);
foreach (Feature f in features)
{
f.Draw(graphic, map, textBox_Attr_Name.Text);
}
graphic.Dispose();
}
至于这个this.mapUpdate()方法是什么内容,顾名思义,想必大家已经可以猜到,那就是重绘地图了。至于定义也很简单,相对之前Form内的btn_MapUpdate_Click函数基本没有改动,现在将其单独提取出来。
SimpleButton btn = (SimpleButton)sender;
MapAction action = MapAction.ZoomIn;
if (btn == btn_MoveIn) action = MapAction.ZoomIn;
else if (btn == btn_ZoomOut) action = MapAction.ZoomOut;
else if (btn == btn_MoveLeft) action = MapAction.MoveLeft;
else if (btn == btn_MoveRight) action = MapAction.MoveRight;
else if (btn == btn_MoveUp) action = MapAction.MoveUp;
else if (btn == btn_MoveDown) action = MapAction.MoveDown;
mapExtent.UpdateExtent(action);
this.mapUpdate();
成果检验
定义一组测试点数据:
1.(100,100)ID:10000
2.(400,100)ID:40000
3.(280,350)ID:28035
将当前视图地理范围定为(0,0)(600,400),确保能装下这三个坐标点。
下边就是测试效果啦
最后添加界面重绘事件
private void FormFourthPart_Paint(object sender, PaintEventArgs e)
{
mapUpdate();
}
This is the map zoom。怎么样,效果还满意么
本篇如此。看好关注,下期见!
Q: 等等!!这动图后边的滚轮缩放什么情况?!代码根本没提这事!
A: 那好吧需要说明,任意点滚轮缩放并非是本篇的完善功能。因为本篇的地图缩放基点是位于视图中心点的,不能以视图的任意位置作为缩放基点。完善的功能将在以后继续探讨
如果你想先体验效果,可以用一行代码来手动注册窗体MouseWheel事件。
this.MouseWheel+=new MouseEventHandler(FormFourthPart_MouseWheel);
//滚轮缩放
private void FormFourthPart_MouseWheel(object sender, MouseEventArgs e)
{
if (e.Delta > 0)//放大
{
mapExtent.UpdateExtent(MapAction.ZoomIn);
}else
mapExtent.UpdateExtent(MapAction.ZoomOut);
this.mapUpdate();
}