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

 

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