第2篇. 坦率的講,這很可能是一篇可將Geometry和你說清楚的文章

手寫地理信息組件系列  第2篇
GIS基礎對象的類體系
難度指數:★★☆☆☆

前情回顧

上一篇是系列內容的第一篇。我們通過應用節點Vertex構建出了基本的空間對象點線面,並用這些對象構造了一個簡單的GIS小玩具玩了起來。這一篇將在前篇的基礎上,系統化GIS的基礎對象。並應用這種更完善的對象體系,再次構建這個簡單的地圖程序,體會其應用的便捷之處。

空間圖形的抽象

點線面雖然各自表示不同的內容,但是在本質上都屬於同一種概念,就是圖形學中的幾何(Geometry)

 

我們經常可以看到Geometry這個名詞,爲什麼要對點線面做抽象呢,其實道理也簡單,和麪向對象設計的要求基本一致,那就是複用。優點是可以很優雅的將子類的共同特徵收集起來,用以簡化子類的設計。

回到點線面的共同特徵,我們現在找到了兩個,那就是位置範圍。位置的表示很好辦,用一個點(Vertex)就能表示,這裏我們用圖形的質心(centroid)來表示圖形的位置。但是點線面的範圍大小如何描述?它們的形狀可以各不相同,用哪種結構可以表達這種概念?

想必小夥伴已經可以猜到,那就是Extent。這個概念也算是很常見的了,但是各商業軟件或者開源組件對它的表達詞彙有所不同,有叫Bounds的,也有叫Boundary、BoundingBox或者MBR的,本質都是這個概念。我更傾向於叫它外接矩形,這個詞彙對我來說更形象,也好理解。

點是沒有面積的,自然它的Extent就是它本身。線的Extent就是以折線的兩個結點(node)爲對角線形成的矩形。而多邊形的Extent就是構成多邊形一系列節點中,最小與最大座標圍成的矩形。

值得注意的是,嚴謹的講,面的外接矩形與其最小外接矩形,是兩種經常容易被混淆的概念。外接矩形通常是指一個平行於兩個座標軸的矩形,而最小外接矩形(SMBR)不一定平行於座標軸,但它的面積應該是所有外接矩形中最小的。如果不嚴格區分,兩個詞都代表平行於座標軸的那一個。

將以上概念用代碼形式做個表示,這裏以Geometry抽象類的形式抽象幾何圖形,以實現複用。

    public abstract class Geometry
    {
        //質心點
        protected Vertex centroid;

        //外接矩形
        protected Extent extent;

        //爲了類的安全性,訪問器只設置get,一經初始化,不可由非子類更改
        public Vertex Centroid
        {
            get { return centroid; }
        }

        public Extent Extent
        {
            get { return extent; }
        }

        public abstract void Draw(Graphics graphics);
    }

    //外接矩形
    public class Extent
    {
        //左下
        public Vertex bottomLeft;

        //右上
        public Vertex upRight;

        public Extent(Vertex bottomleft, Vertex upright)
        {
            this.bottomLeft = bottomleft;
            this.upRight = upright;
        }
    }


細心的小夥伴可能注意到,C#中的set get訪問器,抽象類中只用到了一個。這裏的目的是防止由非子類對象對其進行修改。可以設想一下,如果我們實例化了一個線對象,線對象的兩個端點座標已經確定,這個時候我們對中點或者外接矩形座標進行重新賦值,勢必造成這幾種座標邏輯上不一致的問題,所以centroid和extent要在子類創建的時刻就確定下來,不給別人中途改變它的機會。

點線面子類

點線面子類由Geometry父類繼承而來,繼承了父類屬性和抽象方法,在其實例化時爲父類賦值,這樣外部對象就可以直接訪問父、子對象開放的成員方法及變量,實現客戶端邏輯。以下只實現了點對象的內容,線面對象的實現將在以後的系列補充。


    class Point : Geometry
    {
        public Point(Vertex vertex)
        {
            //爲父類屬性賦值
            centroid = vertex;
            extent = new Extent(vertex, vertex);
        }

        //計算兩點距離
        public double Distance(Vertex another)
        {
            return centroid.Distance(another);
        }

        //繪製
        public override void Draw(Graphics graphics)
        {
            graphics.FillEllipse(new SolidBrush(Color.Red),
              new Rectangle((int)Centroid.x, (int)Centroid.y, 5, 5));
        }
    }

    //線實體
    class Line : Geometry
    {
        List<Vertex> vertexs;

        public override void Draw(Graphics graphics)
        {
        }
    }

    //面實體
    class Polygon : Geometry
    {
        List<Vertex> vertexs;

        public override void Draw(Graphics graphics)
        {
        }
    }


空間實體的屬性

空間實體的屬性在上一篇中已經有所涉及,當時是將屬性與圖形封裝在一個對象當中的,現在需要將其分拆出來,分別表示。將屬性分拆爲Attribute類表示,並用鍵值對存儲。

    class Attribute
    {
        //C#中用以存儲鍵值對的一種容器類,這裏用來存儲字段名和字段值
        private Hashtable table = new Hashtable();

        public void AddValue(string fieldName, Object value)
        {
            table.Add(fieldName, value);
        }
        public Object GetValue(string fieldName)
        {
            return table[fieldName];
        }

        //界面繪製字段值
        public void Draw(Graphics graphics, Vertex location, string key)
        {
            string val =table[key].ToString();
            graphics.DrawString(val, new Font("宋體", 20),
                new SolidBrush(Color.Blue), new PointF((int)location.x, (int)location.y));
        }
    }



空間對象的完整描述-要素 

描述現實世界的一個對象,不僅需要描述其位置、大小等幾何要素,還要結合其屬性進行完整的描述,例如一個城市可以用多邊形來描述其空間,再用GDP、人口等指標描述其屬性,兩者組合起來,就構成了一個要素(Feature)。簡單來說Geometry+Attribute構成了Feature。

    class Feature
    {
        private Geometry geometry;
        private Attribute attribute;
        public Feature(Geometry geometry, Attribute attribute)
        {
            this.geometry = geometry;
            this.attribute = attribute;
        }

        public void Draw(Graphics graphics, string fieldName)
        {
            geometry.Draw(graphics);
            attribute.Draw(graphics, geometry.Centroid, fieldName);
        }

        public Geometry GetGeometry()
        {
            return geometry;
        }

        public Object GetAttributeValue(String fieldName)
        {
            return attribute.GetValue(fieldName);
        }
    }




幾何-屬性-要素這三種概念已經分別實現出來,形成以下類圖。 

類圖中,空心箭頭代表繼承關係;短線箭頭代表組合關係。以上通過對空間對象的分解和組合,相信在你的頭腦裏,已經形成了空間對象的這一套概念模型。

照例,仍然將設計界面拋出來,梳理調用邏輯。

“添加”分組框中的X,Y照例是輸入圖形的座標,Name和Value框是待添加圖形的屬性名和屬性值。
“查詢”分組框中的顯示字段輸入框(DisplayField),用以設置鼠標點選圖形時,顯示圖形的哪個屬性。

以下實現:註冊按鈕點擊事件,生成要素,保存要素集合。


        List<Feature> features = new List<Feature>();

        private void BtnAddPoint_Click(object sender, EventArgs e)
        {
            double x = Convert.ToDouble(textBox_X.Text);
            double y = Convert.ToDouble(textBox_Y.Text);

            //構造圖形
            Vertex vertex = new Vertex(x, y);
            GisClass.Point p = new GisClass.Point(vertex);

            //構造屬性
            GisClass.Attribute attr = new GisClass.Attribute();
            attr.AddValue(textBox_Attr_Name.Text, textBox_Attr_Value.Text);

            //構造要素
            Feature feature = new Feature(p, attr);
            feature.Draw(this.CreateGraphics(), textBox_Attr_Name.Text);

            features.Add(feature);
        }

 同樣,註冊窗體鼠標點擊事件,在窗體控件點擊時執行要素查詢,彈出屬性值。這與之前的查詢邏輯基本一致。


        //距離容限值 10 像素
        const int Tolerance = 10;
        private void Form1_MouseClick(object sender, MouseEventArgs e)
        {
            Vertex vertex = new Vertex(e.X, e.Y);
            double minDistance = Double.MaxValue;
            Feature nearest = null;

            //篩選與鼠標點選位置最近的座標點
            foreach (Feature f in features)
            {
                double distance =
                       f.GetGeometry().Centroid.Distance(vertex);

                if (distance < minDistance)
                {
                    nearest = f;
                    minDistance = distance;
                }
            }

            if (nearest != null && nearest.GetGeometry().Centroid.Distance(vertex) < Tolerance)
            {
                MessageBox.Show(
                  nearest.GetAttributeValue(textBox_DisplayField.Text).ToString());
            }
            else
            {
                textBox_X.Text = e.X.ToString();
                textBox_Y.Text = e.Y.ToString();
            }
        }


現在可以做交互層面最後的調用了。這裏輸入了三個點座標,屬性名均爲city,屬性值分別是南京,黃山和武漢,下邊的查詢字段也寫上了city,點擊了武漢點附近,乖乖的彈出了屬性值。

 窗體縮小後再還原時,會發現圖形不見了,需要給窗體或者承載圖形的控件增加重繪

 初始化窗體時註冊事件:

        this.Paint += new System.Windows.Forms.PaintEventHandler(this.FormSecondPart_Paint);
        private void FormSecondPart_Paint(object sender, PaintEventArgs e)
        {
            mapUpdate();
        }

        private void mapUpdate()
        {
            Graphics graphic = this.CreateGraphics();
            //清理界面,準備重繪
            graphic.Clear(this.BackColor);
            foreach (Feature f in features)
            {
                f.Draw(graphic, textBox_DisplayField.Text);
            }
            graphic.Dispose();
        }

從界面操作看來,雖然與“GIS小玩具”沒有太大的差別,但是其背後的實現,已經慢慢地開始變得有理有條。 


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