免费视频淫片aa毛片_日韩高清在线亚洲专区vr_日韩大片免费观看视频播放_亚洲欧美国产精品完整版

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費(fèi)電子書(shū)等14項(xiàng)超值服

開(kāi)通VIP
使用 Visual Basic 9.0 和 WPF 創(chuàng)建動(dòng)態(tài)地圖
本文討論: WPF 依賴屬性 使用 LINQ 查詢 XML 繪制地圖 實(shí)現(xiàn)地圖數(shù)據(jù)可視化
本文使用了以下技術(shù):
WPF, Visual Basic 9.0, LINQ
目錄
WPF 數(shù)據(jù)綁定
應(yīng)用程序?qū)ο竽P?/a>
Map 類(lèi)
導(dǎo)入地圖數(shù)據(jù)
地圖數(shù)據(jù)格式
導(dǎo)入數(shù)據(jù)
繪制地圖
實(shí)現(xiàn)人口數(shù)據(jù)可視化
結(jié)論
一直對(duì)繪制地圖有些著迷。同時(shí)我認(rèn)為 Visual Studio® 2008 和 Visual Basic® 9.0 實(shí)在令人驚奇。因此,當(dāng)我有機(jī)會(huì)體驗(yàn) Visual Studio 并撰寫(xiě)相關(guān)文章時(shí),我認(rèn)為絕佳的主題是關(guān)于使用 Visual Basic 來(lái)繪制地圖的教程。這不僅能夠讓我有機(jī)會(huì)得以展示一些非??岬?Visual Basic 功能,而且還能為您提供一個(gè)可運(yùn)行的示例,您可以此為基礎(chǔ),向自己的程序添加相似功能。
 
我的杰作如圖 1 所示,該圖顯示了利用熱圖來(lái)直觀展示美國(guó)人口的應(yīng)用程序屏幕快照。該應(yīng)用程序的構(gòu)建充分利用了 Windows® Presentation Foundation (WPF) 數(shù)據(jù)綁定基礎(chǔ)結(jié)構(gòu),這讓我能夠有效地將應(yīng)用程序的域特定邏輯和用戶界面可視化分離開(kāi)來(lái)。
圖 1 人口地圖應(yīng)用程序 (單擊該圖像獲得較小視圖)
圖 1 人口地圖應(yīng)用程序 (單擊該圖像獲得較大視圖)
下一部分中,我會(huì)先簡(jiǎn)單介紹一些初步的 WPF 數(shù)據(jù)綁定主題,然后我會(huì)用本文的剩余篇幅詳細(xì)說(shuō)明如何編寫(xiě)該應(yīng)用程序。我會(huì)先介紹應(yīng)用程序的 Visual Basic 對(duì)象模型,然后討論如何使用 Visual Basic 9.0 中創(chuàng)新性的新 XML 功能和 LINQ 來(lái)實(shí)現(xiàn)數(shù)據(jù)處理邏輯。最后我將展示如何使用 WPF 有效地實(shí)現(xiàn)應(yīng)用程序數(shù)據(jù)可視化。
WPF 數(shù)據(jù)綁定
WPF 數(shù)據(jù)綁定基礎(chǔ)結(jié)構(gòu)基于依賴對(duì)象和依賴屬性概念。依賴對(duì)象提供對(duì)更改通知及動(dòng)態(tài)提取和檢索屬性值功能的支持。依賴屬性則是與依賴對(duì)象關(guān)聯(lián)的屬性。兩者共同構(gòu)成了 WPF 眾多方面(包括數(shù)據(jù)綁定、動(dòng)畫(huà)和樣式)背后的核心基礎(chǔ)結(jié)構(gòu)。
依賴對(duì)象由從 DependencyObject 類(lèi)派生的類(lèi)型的實(shí)例來(lái)表示。同樣,依賴屬性由已注冊(cè)到 WPF 屬性系統(tǒng)的 DependencyProperty 實(shí)例來(lái)表示。
用于應(yīng)用程序數(shù)據(jù)模型時(shí),依賴屬性可大大簡(jiǎn)化代碼。它們?cè)试S直接實(shí)現(xiàn)許多任務(wù),而無(wú)需程序員顯式操作控件。通過(guò) WPF 聲明性數(shù)據(jù)綁定和樣式機(jī)制,更改會(huì)自動(dòng)傳播到用戶界面,而無(wú)需自定義邏輯。
這使得 WPF 用戶界面底層的代碼更易于閱讀、編寫(xiě)和維護(hù)。它還倡導(dǎo)各層之間更好的分離,從而提升到更簡(jiǎn)明的應(yīng)用程序體系結(jié)構(gòu)。最后(也是最重要的),它可讓您充分利用諸如 Expression BlendTM 之類(lèi)的工具,以可視化方式(注重樣式元素而非應(yīng)用程序邏輯)來(lái)設(shè)計(jì)用戶界面。
如需使用依賴屬性的簡(jiǎn)單示例,請(qǐng)參見(jiàn)圖 2。其中我展示了一部分 MapRegion 類(lèi)定義。MapRegion 繼承自 DependencyObject。其主要用途是表示地圖內(nèi)的地理區(qū)域。它聲明了兩個(gè)依賴屬性:RegionName 和 IsSelected。依賴屬性通過(guò)調(diào)用 DependencyProperty 類(lèi)上的共享 Register 方法進(jìn)行聲明,提供了屬性名稱(chēng)、其類(lèi)型和包含該屬性的類(lèi)的類(lèi)型(此例中為 MapRegion)。之后,返回的依賴屬性會(huì)分配給類(lèi)上聲明的公共、共享、只讀字段。這一過(guò)程主要是為了遵循 WPF 約定,將依賴屬性的元數(shù)據(jù)公開(kāi)為定義它們的類(lèi)上的共享字段。
然后,依賴屬性通過(guò)包裝屬性(調(diào)用 DependencyObject 類(lèi)中定義的 GetValue 和 SetValue 方法)向代碼公開(kāi),將適當(dāng)?shù)?DependencyProperty 對(duì)象提供為參數(shù)。這使得 MapRegion 類(lèi)的使用者能夠像訪問(wèn)其他任何普通的實(shí)例屬性一樣訪問(wèn)依賴屬性,同時(shí)充分利用 WPF 中豐富的依賴屬性系統(tǒng)。
值得一提的是,實(shí)際的屬性數(shù)據(jù)并沒(méi)有直接存儲(chǔ)為 MapRegion 類(lèi)中的字段。實(shí)際上,WPF 會(huì)管理依賴對(duì)象相關(guān)聯(lián)的每個(gè)依賴屬性的存儲(chǔ),這樣通過(guò)調(diào)用 SetValue 和 GetValue 便可隨時(shí)使用它們。
還應(yīng)注意的是,依賴屬性只能向依賴對(duì)象注冊(cè)一次。如果嘗試使用同一類(lèi)型的相同名稱(chēng)注冊(cè)兩個(gè)依賴屬性,則會(huì)引發(fā)異常。
在我的示例中,我通過(guò)使用調(diào)用 DependencyProperty.Register 的顯式初始化表達(dá)式,來(lái)聲明共享的只讀字段,從而確保每個(gè)依賴屬性只注冊(cè)一次。這會(huì)導(dǎo)致注冊(cè)過(guò)程作為 MapRegion 類(lèi)靜態(tài)構(gòu)造函數(shù)的一部分來(lái)執(zhí)行,而公共語(yǔ)言運(yùn)行庫(kù) (CLR) 確保會(huì)運(yùn)行一次(在執(zhí)行引用 MapRegion 類(lèi)的代碼之前)。
圖 3 所示的 XAML 代碼顯示了依賴屬性如何用于將域模型的更改自動(dòng)傳播到應(yīng)用程序用戶界面的示例。它定義了一個(gè)樣式,當(dāng)應(yīng)用于 Polygon 控件后,在其關(guān)聯(lián)的 MapRegion 將 IsSelected 屬性設(shè)為 true 時(shí),會(huì)使多邊形呈現(xiàn)為橙色粗邊框,否則為黑色細(xì)邊框。這讓驅(qū)動(dòng)應(yīng)用程序的代碼能夠輕松地將區(qū)域標(biāo)記為選定,并讓用戶界面相應(yīng)作出響應(yīng)。
應(yīng)用程序?qū)ο竽P?div style="height:15px;">
WPF 數(shù)據(jù)綁定是相當(dāng)出色的,因此我著手將它盡可能地用于我的應(yīng)用程序。我的目的是能夠編寫(xiě)清晰、簡(jiǎn)潔和域特定的代碼來(lái)驅(qū)動(dòng)應(yīng)用程序,并能夠使用 Expression Blend 來(lái)設(shè)計(jì)其界面。
為實(shí)現(xiàn)這一點(diǎn),我創(chuàng)建了可充分利用 WPF 依賴屬性系統(tǒng)的對(duì)象模型來(lái)表示地圖信息。該模型由三個(gè)主要類(lèi)組成 — Maps、MapRegion 和 MapPolygon — 全部從 DependencyObject 派生而來(lái)。Map 類(lèi)是對(duì)象模型的根,用于表示包含區(qū)域列表的地圖。MapRegion 類(lèi)則表示地圖上的區(qū)域或地理實(shí)體。它包含一個(gè)多邊形列表,每個(gè)多邊形均描述定義區(qū)域的連續(xù)地理范圍之一的邊界。MapPolygon 類(lèi)表示區(qū)域內(nèi)的多邊形,包含了描述其頂點(diǎn)的 Point 對(duì)象集合。
Map 類(lèi)有五個(gè)依賴屬性:BoundingBox、ScaleX、ScaleY、TranslateX 和 TranslateY。BoundingBox 屬性是 Rect 類(lèi)的實(shí)例,存儲(chǔ)了包含地圖上所有區(qū)域的最小矩形范圍。ScaleX、ScaleY、TranslateX 和 TranslateY 屬性是為支持地圖上的縮放和平移而定義的。這些屬性至關(guān)重要,因?yàn)榈貓D上各點(diǎn)的坐標(biāo)表示從赤道和本初子午線的交叉點(diǎn)開(kāi)始的偏移量(以千米為單位)。另一方面,WPF 使用不同的坐標(biāo)系統(tǒng),按從窗口左上角開(kāi)始的偏移量進(jìn)行定義,以邏輯像素為單位(一個(gè)邏輯像素等于 1/96 英寸)。
因此,ScaleX、ScaleY、TranslateX 和 TranslateY 屬性用于執(zhí)行下列操作:
縮放地圖以便適應(yīng)窗口大小。 垂直翻動(dòng)地圖,以便它不會(huì)繪制成上下顛倒。(在世界視圖中,Y 坐標(biāo)會(huì)隨著您向北行進(jìn)而增加;而在 WPF 視圖中,Y 坐標(biāo)會(huì)隨著您向窗口底部移動(dòng)而增加。) 移動(dòng)地圖以便其左上角對(duì)應(yīng)于顯示地圖的控件的左上角。
 
您還可以使用這些屬性來(lái)實(shí)現(xiàn)用戶界面內(nèi)的縮放和平移功能。例如,將 ScaleX 值和 ScaleY 值乘以 1.5 會(huì)使地圖放大到 150%,將其 TranslateX 屬性加 50 則會(huì)使地圖視圖向西移動(dòng) 50 千米。值得注意的是,更改這些值不會(huì)改變地圖中的坐標(biāo)。實(shí)際上,這些值是用于向用戶界面?zhèn)鬟_(dá)顯示地圖所需的操作。
Map 類(lèi)通過(guò) Regions 屬性公開(kāi)其區(qū)域集合。但是,Regions 與類(lèi)上的其他屬性不同,它不是依賴屬性,而是類(lèi)型為 ObservableCollection(of MapRegion) 的只讀屬性。它沒(méi)有被聲明為依賴屬性,那是因?yàn)槲也恍枰軌蛞淮畏峙湔麄€(gè)集合。然而,該集合本身是可變的,并且可使用 ObservableCollection 類(lèi)的 Add 和 Remove 方法在地圖上添加或刪除區(qū)域。同時(shí),由于該集合公開(kāi)為 ObservableCollection,因此它仍參與 WPF 數(shù)據(jù)綁定系統(tǒng)。例如,如果 ListBox 與 ObservableCollection 進(jìn)行了數(shù)據(jù)綁定,則向該集合添加新的項(xiàng)目便會(huì)導(dǎo)致該新項(xiàng)出現(xiàn)在 ListBox 中。同樣,從集合刪除項(xiàng)目也會(huì)導(dǎo)致從 ListBox 中刪除它。
Map 類(lèi)
MapRegion 類(lèi)定義了兩個(gè)依賴屬性:BoundingBox 和 RegionName。BoundingBox 屬性定義包含區(qū)域的矩形范圍,RegionName 則存儲(chǔ)區(qū)域名稱(chēng)。該類(lèi)還定義了兩個(gè)集合屬性:Polygons 和 FipsCodes。Polygons 集合存儲(chǔ)定義區(qū)域的多邊形,F(xiàn)ipsCodes 則存儲(chǔ)與區(qū)域相關(guān)的一組數(shù)字 FIPS(聯(lián)邦信息處理標(biāo)準(zhǔn))代碼。該類(lèi)使用 FIPS 代碼,因?yàn)樵趹?yīng)用程序中使用的多邊形數(shù)據(jù)是從美國(guó)人口普查局下載的,它使用 FIPS 代碼來(lái)唯一標(biāo)識(shí)美國(guó)的地理實(shí)體。您可以從itl.nist.gov/fipspubs/by-num.htm 下載關(guān)于聯(lián)邦信息處理標(biāo)準(zhǔn)的詳細(xì)信息。此外,還可以從census.gov/geo/www/cob 訪問(wèn)關(guān)于美國(guó)人口普查局的公共域 TIGER 地理數(shù)據(jù)庫(kù)的技術(shù)信息。
MapPolygon 類(lèi)定義了兩個(gè)屬性。第一個(gè)是名為 Region 的只讀依賴屬性,存儲(chǔ)對(duì)包含多邊形的區(qū)域的引用。第二個(gè)是名為 Points 的集合屬性,存儲(chǔ)定義多邊形頂點(diǎn)的一組 Point 結(jié)構(gòu)。在圖 4 中可以看到該類(lèi)的完整源代碼。
Region 屬性作為只讀依賴屬性公開(kāi)。這使得 WPF 屬性系統(tǒng)能夠檢測(cè)對(duì)屬性值的更改,但不允許 WPF 修改該屬性值。只讀依賴屬性是通過(guò)調(diào)用 DependencyProperty 類(lèi)的 RegisterReadOnly 方法來(lái)聲明的。與 Register 方法不同,RegisterReadOnly 會(huì)返回 DependencyPropertyKey 實(shí)例而不是返回 DependencyProperty。
依賴屬性鍵是可實(shí)現(xiàn)對(duì)只讀依賴屬性進(jìn)行修改的對(duì)象。這些對(duì)于實(shí)現(xiàn)類(lèi)(需要內(nèi)部更新屬性值,并由其他類(lèi)型檢測(cè)到更改,但不允許對(duì)值進(jìn)行外部修改)非常有用。因此,DependencyPropertyKey 通常不會(huì)由定義它們的類(lèi)公開(kāi)。這便是 RegionPropertyKey 字段標(biāo)記為私有的原因。之后,第二個(gè)共享字段 RegionProperty 會(huì)用于公開(kāi)提供它的只讀別名。
Region 屬性為只讀屬性,因?yàn)樗O(shè)計(jì)為無(wú)法從外部進(jìn)行編輯。實(shí)際上,當(dāng)在 MapRegion 類(lèi)的區(qū)域集合中添加或刪除多邊形時(shí),它會(huì)自動(dòng)設(shè)置和清除值。這是通過(guò)以下方式實(shí)現(xiàn)的:處理多邊形集合的 CollectionChangedEvent 并將適當(dāng)?shù)?Region 實(shí)例傳播到受影響的多邊形。
事件處理程序的實(shí)現(xiàn)如圖 5 所示。它通過(guò)調(diào)用存儲(chǔ)于處理程序 NotifyCollectionChangedEventArgs 參數(shù)中 NewItems 和 OldItems 集合上的 SetRegion 擴(kuò)展方法來(lái)實(shí)現(xiàn)。之后該擴(kuò)展方法會(huì)在目標(biāo)集合中每一項(xiàng)上調(diào)用 MapPolygon 類(lèi)的 SetRegion 方法。該實(shí)例方法已標(biāo)記為友元,以避免被調(diào)用。該擴(kuò)展方法的實(shí)現(xiàn)如下所示:
<Extension()> _Sub SetRegion(ByVal x As IEnumerable(Of MapPolygon), _ByVal r As MapRegion)For Each item In xitem.SetRegion(r)NextEnd Sub
 
與 Map 和 MapRegion 類(lèi)上的集合屬性不同,MapPolygon 類(lèi)上的 Points 集合不是 ObservableCollection(of T)。而是使用了內(nèi)置 WPF PointsCollection 類(lèi)。這與 WPF Polygon 控件用于存儲(chǔ)它所含一組頂點(diǎn)的類(lèi)型相同。通過(guò)在數(shù)據(jù)模型中使用此類(lèi),便可實(shí)現(xiàn)將 Polygon 控件與 MapPolygon 實(shí)例直接進(jìn)行數(shù)據(jù)綁定。
導(dǎo)入地圖數(shù)據(jù)
應(yīng)用程序從存儲(chǔ)于磁盤(pán)上的 XML 文件加載其地圖數(shù)據(jù)。我通過(guò)從美國(guó)人口普查局下載的原始 ASCII 制圖邊界文件創(chuàng)建文件,其中說(shuō)明了美國(guó)每個(gè)州和縣(及等同范圍)的邊界。州邊界數(shù)據(jù)用于繪制美國(guó)地圖,縣邊界數(shù)據(jù)則用于實(shí)現(xiàn)地圖上人口數(shù)據(jù)可視化。然后我取得下載的數(shù)據(jù),并將其轉(zhuǎn)換為 XML 文件以便處理。本文的下載提供了將文件轉(zhuǎn)換為 XML 的代碼??蓮?a target="_blank" >census.gov/geo/www/cob/bdy_files.html 下載原始數(shù)據(jù)文件。
使用 Visual Studio Create Schema 功能,我便能夠自動(dòng)生成描述 XML 文件內(nèi)容的 XML 架構(gòu)。這樣我便能夠使用新的 Visual Basic XML IntelliSense® 功能,它可依據(jù)項(xiàng)目中包括的任何架構(gòu)文件顯示 XML 成員訪問(wèn)表達(dá)式(參見(jiàn)圖 6)。
圖 6 Visual Basic XML IntelliSense (單擊該圖像獲得較小視圖)
圖 6 Visual Basic XML IntelliSense (單擊該圖像獲得較大視圖)
地圖數(shù)據(jù)格式
描述 XML 地圖文件的架構(gòu)定義了一種簡(jiǎn)單格式,其中包含一個(gè)根 File 標(biāo)記,它隨之又包含一系列 Region 標(biāo)記。每個(gè) Region 標(biāo)記都包含一系列 FipsCode 和 Polygon 標(biāo)記,每個(gè) Polygon 標(biāo)記則包含一系列 Vertex 和 Island 標(biāo)記。
Region 標(biāo)記用于描述簡(jiǎn)單的區(qū)域。它定義了兩個(gè)必需的屬性:Type 和 Name。Type 屬性提供區(qū)域類(lèi)型的描述。就我的應(yīng)用程序而言,我通常使用值 State 或 County。但是,在一般情況下,所有值均可用于 Type 屬性。Name 屬性則說(shuō)明區(qū)域的名稱(chēng)。
區(qū)域中定義的每個(gè) FipsCode 標(biāo)記均說(shuō)明其中一個(gè) FIPS 代碼項(xiàng)。Region 內(nèi)的最后一個(gè) FipsCode 標(biāo)記定義了區(qū)域的數(shù)字 ID。其他 FipsCode 標(biāo)記則遞歸描述區(qū)域各父項(xiàng)的數(shù)字 ID。
就我的應(yīng)用程序而言,所有區(qū)域最多具有兩個(gè)相關(guān)的 FipsCode 值。特別是,描述州(或者州等同范圍)的區(qū)域通常具有一個(gè)項(xiàng),描述與該州相關(guān)的 FIPS 代碼。描述縣(或縣等同范圍)的 Regions 通常具有兩個(gè)項(xiàng),第一項(xiàng)表示包含縣的州 FIPS 代碼,第二項(xiàng)則表示縣本身的 FIPS 代碼。任何特定區(qū)域的數(shù)字 ID 通常都保證在其直接父項(xiàng)的范圍內(nèi)唯一。
區(qū)域的每個(gè) Polygon 標(biāo)記描述了區(qū)域內(nèi)包含的單個(gè)多邊形。多邊形由一系列 Vertex 標(biāo)記組成,每個(gè)標(biāo)記描述了單個(gè)頂點(diǎn)。Vertex 標(biāo)記定義了五個(gè)必需的屬性:Ordinal、Longitude、Latitude、X 和 Y。Ordinal 屬性定義了多邊形頂點(diǎn)的順序。其主要目的是,當(dāng)頂點(diǎn)由不保留順序的查詢處理時(shí),能夠?qū)旤c(diǎn)進(jìn)行正確排序。它目前尚未用于我的應(yīng)用程序。Longitude 和 Latitude 屬性描述了頂點(diǎn)的經(jīng)度和緯度坐標(biāo)。同樣,X 和 Y 屬性定義了投影到矩形空間的頂點(diǎn)坐標(biāo)和應(yīng)用程序用于繪制地圖的對(duì)象。
X 和 Y 屬性是通過(guò)圖 7 所示的簡(jiǎn)單投影來(lái)計(jì)算的。此投影不是特別準(zhǔn)確,可能不適用于某些情形。在這些情況下,可以使用 Longitude 和 Latitude 而非存儲(chǔ)于文件中的 X 和 Y 值來(lái)直接計(jì)算新值。就我的應(yīng)用程序而言,并不需要高度的地理精確度,這便已足夠。
圖 7 極其簡(jiǎn)單的地圖投影
多邊形內(nèi)的 Island 標(biāo)記定義了包含在其中但不屬于它所含區(qū)域的空洞。它包含了描述空洞邊界的 Vertex 標(biāo)記的集合。我主要出于完整性目的將它們包含在數(shù)據(jù)文件中;但并沒(méi)有用于我的應(yīng)用程序。這意味著實(shí)現(xiàn)地圖數(shù)據(jù)可視化時(shí),地圖上會(huì)有極小的區(qū)域顯示稍有錯(cuò)誤的值。對(duì)于我的應(yīng)用程序而言,這不是個(gè)問(wèn)題,因此我便忽略而過(guò)了。
如果您的應(yīng)用程序在這方面的準(zhǔn)確性至關(guān)重要,或者是多邊形島很普遍的映射區(qū)域,則只需控制地圖的呈現(xiàn)順序便能輕松解決這一問(wèn)題。首先繪制包含島的多邊形,然后繪制上部的島,這樣便可以準(zhǔn)確呈現(xiàn)多邊形島。在很多情況下,這可能需要額外的處理工作,以便確定重疊區(qū)域并構(gòu)造出適當(dāng)?shù)捻樞颉?div style="height:15px;">
導(dǎo)入數(shù)據(jù)
使用 LINQ 將地圖數(shù)據(jù)導(dǎo)入應(yīng)用程序比較簡(jiǎn)單。執(zhí)行這一操作的代碼如圖 8 所示。它定義了接受以下兩個(gè)參數(shù)的過(guò)程 LoadFile:file 和 list。file 參數(shù)是一個(gè)字符串,包含從磁盤(pán)加載 XML 文件的完整路徑。list 參數(shù)則是對(duì) MapRegion 實(shí)例集合的引用。該過(guò)程會(huì)打開(kāi) XML 文件進(jìn)行處理,并將其定義的所有區(qū)域插入到提供的集合。
LoadFile 主體極其簡(jiǎn)單。此過(guò)程的工作方式如下:首先將提供的文件加載到 XDocument 實(shí)例,定義查詢以遍歷文檔并將其轉(zhuǎn)換為 MapRegion 實(shí)例集合,最后執(zhí)行查詢并將其結(jié)果插入提供的輸出集合。
該查詢由多個(gè)不同的片斷組成。第一個(gè)片斷是 From 子句,使用 XML 成員訪問(wèn)表達(dá)式 doc.<File>.<Region>.<Polygon>.<Vertex> 來(lái)檢索文檔中所有的多邊形 Vertex 標(biāo)記,作為查詢的基礎(chǔ)。值得注意的是,它并沒(méi)有使用 doc...<Vertex>,該表達(dá)式可以獲取文檔中所有的 Vertex 標(biāo)記(包括 Island 標(biāo)記內(nèi)部所定義的)。實(shí)際上,它只包括 Polygon 標(biāo)記內(nèi)部直接定義的 Vertex 標(biāo)記。查詢的第二部分是 Let 子句。此子句引入了兩個(gè)新變量,Polygon 和 Region,它們已分配給包含每個(gè)頂點(diǎn)的 Polygon 和 Region 標(biāo)記。
查詢的第三部分是首個(gè) Group By 子句。它能夠輕松地將頂點(diǎn)列表按其包含的多邊形進(jìn)行分組,然后計(jì)算多邊形的邊界框。包含多邊形的區(qū)域也包含在分組鍵中,使它能夠用于未來(lái)的查詢。多邊形的邊界框是通過(guò)計(jì)算其頂點(diǎn) X 和 Y 屬性的最小和最大值來(lái)計(jì)算的。這通過(guò)調(diào)用自定義聚合函數(shù) MinDBL 和 MaxDBL 來(lái)完成,兩者由兩個(gè)擴(kuò)展方法所定義:
<Extension()> _Function MinDBL(Of T)(ByVal x As IEnumerable(Of T), _ByVal y As Func(Of T, String)) As DoubleReturn x.Min(Function(z) CDbl(y(z)))End Function<Extension()> _Function MaxDBL(Of T)(ByVal x As IEnumerable(Of T), _ByVal y As Func(Of T, String)) As DoubleReturn x.Max(Function(z) CDbl(y(z)))End Function
 
這些方法用于定義聚合函數(shù),聚合函數(shù)首先將提供的參數(shù)轉(zhuǎn)換為 Double 類(lèi)型,再根據(jù)它計(jì)算最小或最大值。
Group By 子句之后是 Select 子句,可按結(jié)果將原始分組轉(zhuǎn)換為更好用的格式。特別是,它定義了一個(gè)子查詢,使用 Group By 產(chǎn)生的分組,只從中投影出頂點(diǎn),然后使用 ToMapPolygon 擴(kuò)展方法將產(chǎn)生的集合轉(zhuǎn)換為 MapPolygon 類(lèi)的實(shí)例:
<Extension()> _Function ToMapPolygon(ByVal items As IEnumerable(Of XElement)) As _MapPolygonDim ret As New MapPolygonret.Points.AddRange(From item In items Select item.ToPoint())Return retEnd Function<Extension()> _Function ToPoint(ByVal x As XElement) As PointReturn New Point(x.@X, x.@Y)End Function
 
它還會(huì)將原始 MinX、MinY、MaxX 和 MaxY 變量轉(zhuǎn)換為 Rect 類(lèi)的實(shí)例。
查詢的下一部分是它的第二個(gè) Group By 子句,可聚合先前根據(jù) Region 選擇的結(jié)果,并計(jì)算包含所有區(qū)域多邊形的邊界框。邊界框是通過(guò) Enclose 自定義聚合函數(shù)計(jì)算的,該函數(shù)定義如圖 9 所示。查詢的最后一部分是最終的 Select 子句,可為從第二個(gè) Group By 返回的每個(gè)結(jié)果構(gòu)造 MapRegion 實(shí)例。它通過(guò)兩個(gè)子查詢來(lái)完成這項(xiàng)工作,其中一個(gè)可投影出存儲(chǔ)于每個(gè) Group 成員中的多邊形實(shí)例,另一個(gè)則提取存儲(chǔ)于 Region 中的一組 FIPS 代碼。
繪制地圖
地圖加載到內(nèi)存后,將它顯示在窗口中便輕而易舉。圖 10 中的 XAML 代碼顯示了完成此工作的簡(jiǎn)便方法。此處我定義了一個(gè)簡(jiǎn)單的 Canvas 控件,包含其 Render Transform 的兩種轉(zhuǎn)換,以及名為 MapViewer 的單個(gè) ItemsControl 作為其內(nèi)容。這些轉(zhuǎn)換用于轉(zhuǎn)換地圖中區(qū)域的坐標(biāo),以便它們能正確顯示在畫(huà)布中。ItemsControl 用于以可視方式呈現(xiàn)地圖的內(nèi)容。所有數(shù)據(jù)綁定都通過(guò)使用隱式數(shù)據(jù)環(huán)境來(lái)完成。就 MasterCanvas 控件而言,這通常設(shè)為 Map 類(lèi)的實(shí)例。
TranslateTransform 將其 TranslateX 和 TranslateY 屬性綁定到 Map 類(lèi)的等效屬性。通常,這些值在 Map 類(lèi)上設(shè)為 Map 邊界框左上角的反值。這具有移動(dòng)地圖內(nèi)容的效果,以便其左上角與畫(huà)布的左上角相對(duì)應(yīng)。同樣,ScaleTransform 將其 ScaleX 和 ScaleY 屬性綁定到 Map 類(lèi)上相關(guān)的屬性。在大多數(shù)情況下,它們?cè)O(shè)為畫(huà)布寬度與地圖寬度之正比,以及畫(huà)布高度與地圖高度之反比。
以下代碼顯示了 Map 類(lèi)中 ScaleTo 方法的實(shí)現(xiàn):
Public Sub ScaleTo(ByVal size As Size)If BoundingBox.Width <> 0 AndAlso BoundingBox.Height <> 0 ThenScaleX = size.Width / BoundingBox.WidthScaleY = -size.Height / BoundingBox.HeightTranslateX = -BoundingBox.LeftTranslateY = -BoundingBox.BottomEnd IfEnd Sub
 
給定包含 MasterCanvas 高度和寬度的 Size 結(jié)構(gòu)時(shí),該方法會(huì)轉(zhuǎn)換地圖,以便在畫(huà)布上完整顯示地圖。調(diào)用此方法是為了響應(yīng)畫(huà)布的 SizeChanged 事件,以便畫(huà)布所含窗口重新調(diào)整大小時(shí),地圖能夠縮放以填充可用空間:
Private Sub WindowSizeChanged() Handles Me.SizeChangedIf m_Map IsNot Nothing Thenm_Map.ScaleTo(New Size(MasterCanvas.ActualWidth,MasterCanvas.ActualHeight))End IfEnd Sub
 
值得注意的是,所含 TransformGroup 內(nèi)轉(zhuǎn)換的順序相當(dāng)重要。特別是,TranslateTransformation 必須在 ScaleTransform 之前發(fā)生。這允許兩個(gè)轉(zhuǎn)換的參數(shù)可依據(jù)域模型(而非用戶界面)指定。如果先列出 ScaleTransform,則 TranslateTransform 的值可能需要以像素(而非千米)來(lái)指定,這需要顯式轉(zhuǎn)換。首先指定 TranslateTransform 可大大簡(jiǎn)化應(yīng)用程序的對(duì)象模型。
ItemsControl 用于將地圖內(nèi)容呈現(xiàn)到畫(huà)布上。它的行為類(lèi)似于 ListBox 控件。實(shí)際上,ItemsControl 是 ListBox 的基類(lèi),定義了大多數(shù)的數(shù)據(jù)綁定行為。但是,與 ListBox 不同的是,ItemsControl 不會(huì)將項(xiàng)目呈現(xiàn)為列表形式。這使得它更適用于呈現(xiàn)地圖,因?yàn)榈貓D顯然不應(yīng)呈現(xiàn)為列表形式。
ItemsControl 將其 ItemsSource 屬性綁定到地圖的 Regions 集合,并使用 SimpleCanvasTemplate 和 RegionTemplate 資源來(lái)定義如何呈現(xiàn)所有內(nèi)容。用于 ItemsControl 之 ItemsPanel 屬性的 SimpleCanvasTemplate 僅僅創(chuàng)建一個(gè)空 Canvas。它的目的是定義容器,用于承載由控件的 ItemTemplate 創(chuàng)建的每個(gè)項(xiàng)。使用畫(huà)布的主要原因是畫(huà)布允許顯式定位其內(nèi)容。其他“容器控件”(如 StackPanel)會(huì)嘗試應(yīng)用最終無(wú)法正確繪制地圖的自動(dòng)布局邏輯。
RegionTemplate 資源定義了用于地圖中每個(gè) Region 的可視化布局。其工作方式是為每個(gè)區(qū)域聲明一個(gè)嵌套 ItemsControl,并將其 ItemSource 綁定到區(qū)域的 Polygon 集合即可。與 MapViewer 一樣,RegionTemplate 中定義的嵌套 ItemsControl 使用畫(huà)布作為其 ItemsPanel(實(shí)際上它重用了 SimpleCanvasTemplate)。但是,與 MapViewer 不同的是,它使用 PolygonTemplate 資源來(lái)呈現(xiàn)其項(xiàng)目。
PolygonTemplate 資源定義了一個(gè)數(shù)據(jù)模板,可呈現(xiàn)給定 MapPolygon 類(lèi)的實(shí)例的 WPF 多邊形控件。它將每個(gè) Polygon 的 Points 集合綁定到 MapPolygon 實(shí)例的 Points 屬性。值得一提的是,Polygon 控件將其 Fill 屬性顯式設(shè)為 Transparent。這對(duì)于使 Polygon 能響應(yīng)多個(gè)鼠標(biāo)事件是不可或缺的。WPF 會(huì)區(qū)分設(shè)置了 Fill 屬性和沒(méi)有設(shè)置此屬性的多邊形。缺少 Fill 畫(huà)筆的多邊形被視為外殼多邊形,而顯式設(shè)置了 Fill 畫(huà)筆的多邊形則認(rèn)為是已填充的多邊形。用于已填充多邊形的鼠標(biāo)事件(如 MouseUp、MouseEnter 和 MouseLeave)會(huì)在鼠標(biāo)位于多邊形的內(nèi)部時(shí)激發(fā)。就外殼多邊形而言,圖形的內(nèi)部并不視為其定義的一部分。在此情況下,將鼠標(biāo)置于多邊形的內(nèi)部不會(huì)激發(fā)任何鼠標(biāo)事件。但是,通過(guò)將多邊形的填充內(nèi)容設(shè)為透明便能夠與鼠標(biāo)交互,而無(wú)需遮蔽地圖上繪制的其他數(shù)據(jù)。
多邊形模板的另一個(gè)有趣方面是,它使用了聲明性、基于觸發(fā)器的樣式來(lái)驅(qū)動(dòng)多邊形外觀。特別是,它引用了定義兩個(gè)截然不同的樣式來(lái)呈現(xiàn)多邊形的 RegionPolygonStyle 資源。區(qū)域中 IsSelected 值為 true 的多邊形通過(guò)橙色粗邊框來(lái)呈現(xiàn),IsSelected 值為 false 的則通過(guò)黑色細(xì)邊框來(lái)呈現(xiàn)。以下事件處理代碼顯示了如何輕松地用樣式來(lái)實(shí)現(xiàn)非??岬挠脩艚缑婀δ?,如輸入跟蹤:
Private Sub Polygon_MouseEnter(ByVal sender As Polygon, ByVal e As _System.Windows.Input.MouseEventArgs)CType(sender.DataContext, MapPolygon).Region.IsSelected = TrueEnd SubPrivate Sub Polygon_MouseLeave(ByVal sender As Polygon, ByVal e As _System.Windows.Input.MouseEventArgs)CType(sender.DataContext, MapPolygon).Region.IsSelected = FalseEnd Sub
 
圖 11 顯示了使用中的一個(gè)示例。
圖 11 使用鼠標(biāo)事件觸發(fā)器突出顯示區(qū)域 (單擊該圖像獲得較小視圖)
圖 11 使用鼠標(biāo)事件觸發(fā)器突出顯示區(qū)域 (單擊該圖像獲得較大視圖)
實(shí)現(xiàn)人口數(shù)據(jù)可視化
實(shí)現(xiàn)地圖上方的人口數(shù)據(jù)可視化相當(dāng)簡(jiǎn)單。實(shí)現(xiàn)這一過(guò)程的 XAML 代碼如圖 12 所示。它通過(guò)將第二個(gè)名為 DataLayer 的 ItemsControl 添加到 MasterCanvas,從而修改用于繪制地圖的 XAML。由于 DataLayer 位于 MasterCanvas 內(nèi),因此其內(nèi)容的轉(zhuǎn)換方式與 MapViewer 相同。這允許綁定到 DataLayer 的數(shù)據(jù)也能夠依據(jù)底層的域模型(而非用戶界面)進(jìn)行指定。
與 MapViewer 不同的是,DataLayer 并不旨在從 MasterCanvas 直接繼承其數(shù)據(jù)環(huán)境,而旨在將要可視化的數(shù)據(jù)顯式設(shè)置為其 ItemsSource。提供的數(shù)據(jù)應(yīng)該是定義了 Color 和 Points 兩個(gè)屬性的對(duì)象集合。Color 屬性應(yīng)為 Brush 類(lèi)型,并定義由 Points 描述的多邊形在地圖上應(yīng)如何著色。Points 屬性應(yīng)為 PointsCollection 的實(shí)例,定義要著色的多邊形的各頂點(diǎn)。以下是用于呈現(xiàn) DataLayer 元素的 DataTemplate:
<DataTemplate x:Key="DataLayerTemplate"><Polygon Stroke="Transparent" Fill="{Binding Path=Color}" Points="{Binding Path=Points}" /></DataTemplate>
 
它的工作方式是創(chuàng)建多邊形并將其 Fill 和 Points 屬性綁定到底層元素上的適當(dāng)屬性即可。
圖 13 所示的 LoadData 方法負(fù)責(zé)將主要的地圖數(shù)據(jù)加載到 Map 類(lèi)的實(shí)例,將其設(shè)為 MasterCanvas 的 DataContext,構(gòu)造熱圖并將 DataLayer 綁定到此。
該方法特別有趣的部分是用于構(gòu)建熱圖的查詢。它的工作方式是聯(lián)接地圖上顯示的一組州、從磁盤(pán)加載的一組縣以及 XML 人口數(shù)據(jù)。它將一組州與一組縣聯(lián)接,以便篩選出州中已定義但地圖上未顯示的縣。該聯(lián)接兩邊的鍵便是每個(gè)區(qū)域的首個(gè) FIPS 代碼。就 State 而言,這將對(duì)應(yīng)于其數(shù)字 ID。就 County 而言,它對(duì)應(yīng)于包含州數(shù)字 ID 的縣。XML 人口數(shù)據(jù)然后與結(jié)果聯(lián)接,以便將每個(gè)縣的人口值與定義該縣的 MapRegion 實(shí)例關(guān)聯(lián)起來(lái)。
聯(lián)接的結(jié)果會(huì)進(jìn)行篩選,以便包括 2006 年度的人口數(shù)據(jù)。這是十分有必要的,因?yàn)?XML 人口數(shù)據(jù)包含多個(gè)年度的數(shù)據(jù),而此案例中,我只想在地圖上顯示一個(gè)特定年度。無(wú)法依據(jù)年度篩選將會(huì)導(dǎo)致在地圖為同一區(qū)域繪制出具有不同值的多個(gè)重疊多邊形。篩選的結(jié)果會(huì)與每個(gè)縣的 Polygons 集合交叉聯(lián)接。這可將縣集合有效擴(kuò)展到多邊形集合。
查詢的最后一部分是 Select 語(yǔ)句,可從每個(gè)多邊形提取點(diǎn)集合,依據(jù)相關(guān)人口值計(jì)算應(yīng)顯示的顏色,然后構(gòu)造以該顏色呈現(xiàn)該區(qū)域的 SolidColorBrush。這些顏色值是使用 colorMapEntries 數(shù)組計(jì)算的,該數(shù)組定義了人口值和顏色之間的映射。實(shí)質(zhì)上,數(shù)組中的每一項(xiàng)都定義了一種顏色和相關(guān)的上限人口值。然后從人口所在的兩個(gè)顏色地圖項(xiàng)之間內(nèi)插用于縣的特定顏色。
該內(nèi)插通過(guò)圖 14 中定義的 Interpolate 擴(kuò)展方法來(lái)實(shí)現(xiàn)。它的工作方式是在 colorMapEntries 數(shù)組中執(zhí)行二分法搜索,查找人口值大于提供值的最小項(xiàng)。然后它取得該項(xiàng)和上一項(xiàng),并用來(lái)內(nèi)插適當(dāng)?shù)念伾?。工作方式是調(diào)用 ColorMapEntry 類(lèi)上定義的 Interpolate 實(shí)例方法。
結(jié)論
我的應(yīng)用程序的最大優(yōu)點(diǎn)在于編寫(xiě)簡(jiǎn)單。特別是,我廣泛使用了 Expression Blend 來(lái)定義其界面,因此使得添加出色的視覺(jué)效果易如反掌。我還使用 XML 來(lái)存儲(chǔ)應(yīng)用程序運(yùn)行所需的所有數(shù)據(jù),這使我能夠使用 Visual Basic 中的新集成 XML 功能以及 LINQ,輕松實(shí)現(xiàn)所有的數(shù)據(jù)操作邏輯。我還將界面設(shè)計(jì)為構(gòu)建于 WPF 數(shù)據(jù)綁定基礎(chǔ)結(jié)構(gòu)之上,因此創(chuàng)建了構(gòu)建于豐富對(duì)象模型之上、清楚分離、結(jié)構(gòu)合理的應(yīng)用程序。
實(shí)質(zhì)上,通過(guò)使用 Visual Basic、WPF、Expression Blend 和 LINQ,我能夠快速有效地從現(xiàn)有的數(shù)據(jù)主體中拼湊出實(shí)現(xiàn)相當(dāng)成熟的可視化的應(yīng)用程序。此應(yīng)用程序能夠很方便地進(jìn)行擴(kuò)展,以便查看不同年度的數(shù)據(jù),或以各種方式操作數(shù)據(jù)。本文的下載提供了所有代碼,請(qǐng)自由試驗(yàn),創(chuàng)造一切可能。
NEW:Explore the sample code online! - or - 代碼下載位置:MapsWithVB9WPF2007_12.exe (30813KB)
本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊舉報(bào)。
打開(kāi)APP,閱讀全文并永久保存 查看更多類(lèi)似文章
猜你喜歡
類(lèi)似文章
用 Python 繪制空間數(shù)據(jù)可視化地圖
.Net5 WPF快速入門(mén)系列教程
WPF Databinding----Collections
ComponentOne DataGrid for WPF 基礎(chǔ)教程:4.列的類(lèi)型
gis原理十六
地理信息系統(tǒng)導(dǎo)論學(xué)習(xí)筆記(11)——矢量數(shù)據(jù)分析
更多類(lèi)似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長(zhǎng)圖 關(guān)注 下載文章
綁定賬號(hào)成功
后續(xù)可登錄賬號(hào)暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服