第4篇. 这位同学,请回答电子地图与纸质地图的区别!

手写地理信息组件系列 第4篇
地图的缩放变换
难度指数:★★☆☆☆

 

目录

前情回顾

地图缩放变换的实质

地图Extent类的增强

实现地图缩放变换的过程

成果检验


前情回顾

前一篇,我们讨论了地图中,关于地图座标与屏幕座标间的互转内容。我们知道了地图显示窗口中,地物的位置和范围是由其Extent决定的,而这一篇将会动态的计算Extent,以实现地图的缩放和平移变换,并在上一篇形成的程序之上加入地图缩放变换能力,进一步加强地图组件的功能!

地图缩放变换的实质

电子地图区别于传统纸质地图的重要表现,在于其自由的缩放和平移。在地图组件中,缩放和平移都是通过调节地图窗口的地理范围得到。这里将地图的缩放和平移分别进行分析:

地图平移:

地图左右平移的实质是x座标的增减,不涉及y座标的变化。所以只需图中的minX、maxX地图座标,在x轴上进行一次统一的加减操作。对象向右移动,两x座标加正数。同理,对象向左移动,x加负数。但是加多少呢,不能简单地固定为一个数值,需要结合当前Extent的宽度确定(如果固定为一个数值会是什么效果?)。

具体做法应是,获取当前Extent的宽度乘以一个比率得到,这个比率一般设为一个小于1的浮点数。如0.2。向右平移的计算方式举例:newMinX = minX + width * 0.2newMaxX = 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();
        }

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章