文章版權由作者李曉暉和博客園共有,若轉載請於明顯處標明出處:http://www.cnblogs.com/naaoveGIS/。
1.前言
在GIS中元素一般分為點元素,線元素,面元素以及symbol元素(特殊的點元素)等。與此對應,圖層可以分為點圖層,線圖層,面圖層以及標注圖層等。從第9章到第10章,我給大家講解了什么是矢量數據、矢量數據的來源、矢量數據的構造、以及矢量數據中的地理坐標與屏幕坐標之間的轉換。在了解了這些概念和算法以及流程后,這一章我們將開始講解設計出一個矢量圖層前的最后一步,設計WebGIS中的要素(Feature)。
2.要素設計的思路
2.1 需求
要素應該具有如下功能:
(1)能夠繪制出矢量數據的Shape。
(2)能夠存儲矢量數據的atrributes。
(3)可以響應鼠標事件。
(4)可以響應自定義事件,如自定義的一系列地圖事件。
(5)能夠添加入Canvas組成一個矢量圖層。
(6)要素是有很多種類的,比如點、線、面等。
2.2 基本分析
(1)在前面我們已經闡述了要素的本質,即UIComponent。因為UIComponent是前端語言中已經封裝好了的類,我們只用繼承其即可,如此我們便同時能實現上面所說的第三個需求以及第五個需求。
(2) 需求中要求要素中能夠繪制出矢量數據的Shape。所以得定義類似於Draw這樣的功能函數,以解決第一個需求。
(3)要素中還需要有個一變量存放attributes,即FeatureInfo以解決第二個需求。
(4)設計的類中還要定義一個監聽事件的函數和一個移除監聽事件的函數,即:addMapEventListener和removeMapEventListener,以解決第四個需求。
2.3 擴展分析
2.3.1重繪
當地圖刷新,比如放大和縮小時,此時的UIComponent必須重繪,否則地圖上的Shape將不再在地圖上表示正確的位置,於是這里對Feature的設計有兩點需要注意:
(1)對地圖放大和縮小進行監聽。
(2)增加重繪函數,即reDraw函數。當地圖放大和縮小時觸發重繪函數。
2.3.2 基類
需求中提到了要素具有多種類型,我們設計時需要將這幾種類型均涉及到的屬性和函數抽象出來作為一個基類,即Feature類。可以抽象出來的屬性和類有:
(1)reDraw和Draw函數
(2)FeatureInfo屬性
(3)addMapEventListener和removeMapEventListener函數
(4)對地圖放大縮小事件的監聽,此功能的實現可以放在Feature的構造函數中。
2.3.3 hook到主框架
在繪制shape時,我們需要用到地理坐標與平面坐標互相轉換的函數,即上一章提到過的geoXYToScreenXY和screenXYToGeoXY。但是這兩種方法是不可能封裝到Feature類中的。因為這兩種方法為系統中通用方法,不是專為Feature類而存在。所以這兩個方法是放在主框架類中,這里我將這個主框架定義為FrameMap。於是,我們的Feature類中還應該有可以注入主框架的屬性,即Map。
3.UML設計
基於上述分析,我們現在要開始真正的設計要素了。我們首先定義一個Feature類,其繼承於UIComponent,然后再根據需求定義一系列具體的基礎要素類,比如PointFeature、LineFeature和PolygonFeature等。並且如果需求出現變更,原有的基礎要素已經不能滿足需求時,可以在基礎要素上進行繼承從而進一步擴展基礎要素類。
如下是最后設計出的要素類的UML圖:
4.問題及解決方法
4.1如何控制每種基礎要素類中要求shape以不同樣式展現的需求?
在實際應用中,對同一個要素,用戶需要的展現方式可能不盡相同。比如對面要素,有時候需要展現成黃色,有時候需要展現成藍色等,而有時候需要面要素內的填充為網格狀,有時候需要面在移動上去時變色,有時候又不需要等等。
解決此問題的方法,並不是去為每種需求重新將基礎要素繼承,然后修改draw方式,如果這樣的話,代碼的重復量太大。在設計模式中,我們有一個原則是能用組合時盡量用組合,能不用繼承時盡量不用繼承,因為繼承是種高度的耦合,派生類和基類被緊緊的綁在一起,靈活性大大降低,而且,濫用繼承,也會使繼承樹變得又大又復雜,很難理解和維護。在這個需求里,目前用不上組合,但是繼承就更不需要了。
我們的解決方法一般是:
(1)首先將需求分類。外觀上:一類是改變顏色的,一類是改變要素內部構造的。行為上:一類是需要某些監聽並且做出相關變化的,一類是不需要某些監聽的。這樣我們可以在具體要素類,比如polygon類中添加兩個共有屬性,一個是DrewType,一個是ActionType。如果不希望把控制行為的重擔都放在polygon類中,我們也可以不用定義ActionType,此類中本來就設計有addMapEventListener和removeMapEventListener。可以開放給調用者,讓其自行判斷。
(2)然后我們在Draw函數中,通過判斷DrawType來進行繪制方面的控制。每個具體要素類中有自己定義的內部透明度(Sopacity)、外部透明度(opacity)以及內部填充色(SColor)和外部填充色(color)以及填充圖標(Symbol)等等繪制方面的屬性。回到上面的需求中,我們一類是想改變要素顏色,一類是想改變內部構造。還是假設這是一個對於polygon要素的需求,我們把DrawType定義為0和1。當DrawType為0時,我們將使用Sopacity,opacity、Scolor和color等繪畫要素進行繪制,這些要素均是有默認值的公有屬性,調用者可以自己設定。但是當DrawType是1時,這時候會將Symbol進行加載,然后改變繪制polygon的方式,將Symbol作為內部填充物進行填充。同樣,Symbol也是有默認值的公有屬性,調用者可以自己設定。
4.2地圖發生平移時,Canvas的左上角坐標也出現平移,如何解決此時要素重繪時要素偏移問題?
在后面探討設計WebGIS的地圖平移功能時,我們會講到每次地圖被拖拽后,矢量圖層(Canvas)的左上角坐標均會加減相同的屏幕平移量。這時候如果僅僅是將要素中的地理坐標轉換為屏幕坐標后就直接將其繪制在UIComponent,然后添加到該Canvas里,便會出現繪制的要素不在地圖上實際的地點的現象,即發生了偏移。這個問題是一個有一定難度的問題,我會在以后的WebGIS基礎功能設計篇里專門花一個章節跟大家探討這個問題。
5.通過設計模式再次探討我們的設計
以上的設計基本上是遵循了面向對象的設計思維。不過,依據設計模式的原則,此中依然有可以改進的地方。
(1)根據依賴倒轉原則,我們可以定義一個接口,比如IFeature,然后基類繼承於此接口,這樣可以通過接口來規范基類應該實現哪些方法。
(2)我們可以考慮使用簡單工廠模式,從而進一步減少界面層類和Feature類中的耦合度。
不過,設計模式除非真有效用時再用才比較好,目前的元素設計在實際的編寫代碼中已經很好用了,當用到工廠類時,需要再次編寫很多類出來,而且效果也不一定好。
6.總結
這一章里我們將設計矢量圖層前的最后一個障礙克服了,但是,我在這個章節中還是留下了一個沒有解答的問題:地圖平移后的要素重繪問題。這個問題,我會在以后的章節中專門跟大家一起探討。下一章,我們就開始走完WebGIS柵格圖層和矢量圖層設計這個系列的最后一站了,希望大家持續關注。
——歡迎轉載,但保留版權,請於明顯處標明出處:http://www.cnblogs.com/naaoveGIS