通過上一節的學習,我們已經知道了如何與GraphicLayer互動,但畢竟GIS不是一個畫板,所以這節來看一下如何通過Silverlight API完成GIS中的分析功能。
GIS之所以是一個通用的工具,就是因為它具有各種各樣分析和處理資料的能力。Silverlight API中提供了Task,使我們能夠輕鬆完成常見的分析任務。
先來考慮一下吃餃子的情境。要想吃餃子,我們需要先去買菜,買肉,回家後在廚房裡洗菜,揉面, 拌餡,包餃子,煮餃子,吃餃子,之後別忘了洗碗;另一種情況就是去飯館,告訴服務員我要吃3兩茴香,3兩韭菜的餃子,然後等著餃子端到你面前,開吃,走人。
在ArcGISServer程式開發中,要完成GIS的分析功能其實和吃餃子是一樣的。用ADF編程就像在家裡吃餃子,除了架設伺服器,所有的工作基本上也都得我們自己在伺服器端來完成,要處理的地方比較多;而用用戶端API編程相當於去外面吃餃子,我們只要把任務交給相應的Task,之後接受結果就行了,不用做餃子。唯一不同的就是在外面吃完餃子別忘了付錢,而用Task完成分析任務則是免費的。這點也體現在使用用戶端API中的Task時,是由ArcGISOnline提供給你的,不需要自己購買AGS軟體。
現在來看看Silverlight API目前給我們提供了那些Task功能:
Query:能夠在已經發布的服務資料中,通過屬性條件(可以屬性欄位中進行關係判斷,字元尋找等),圖形條件(與輸入的圖形相交、包含、相離等),或者是兩者的組合,查詢出滿足條件的資料並返回。相當於Engine中的SpatialFilter,當然也是QueryFilter。
Find:在地圖資料的屬性欄位中尋找包含有關鍵字參數的資料並返回。
Identity:對滑鼠當前點擊位置上的資料進行辨識並返回結果,可以對多個圖層的資料進行辨識。
Address Locator:輸入經緯度,返回地址結果(Geocoding);輸入一個地方的地址,返回經緯度結果(Reverse Geocoding)。由於國內地圖資料保密工作做的相當好,這個Task暫時用不到。
Geometry Service:可以對輸入的地理資料進行如緩衝區,動態投影,面積/周長量算等幾何操作。
Geoprocessing:能夠完成複雜的GIS任務,類似ToolBox中的工具。
抽象一下,可以看出,Query完全可以完成Identity和Find的工作,但後兩者在特定場合下使用起來比Query要方便的多;Geoprocessing完全可以替代Geometry Service,但是在利用REST API編寫的程式中,要盡量使用GeometryService。
再抽象一下,Silverlight API中的這幾個Task和JavaScript/FlexAPI中的Task是大同小異的,因為其實它們都是AGS 9.3 REST API中暴露出來的操作資源(OperationResource)見:
後面的代碼中實際上也是把輸入參數封裝起來提交到了REST API的特定Endpoint上。要理解好用戶端API中的Task,建議熟讀AGS的REST SDK。
Task的用法基本上相同,都遵循這幾個步驟:初始化Task,設定Task所需參數,提交任務,等待伺服器完成任務後,處理返回的結果;進飯館,想好你要吃什麼餃子,告訴服務員,等餃子做好端上來,開始吃。好了,下面我們就通過一個執行個體(點擊這裡,查看執行個體),來學習一下Query和Geometry兩個Task的用法。
首先選擇工具條中的畫線工具,在螢幕上畫一條曲線,會根據曲線自動產生一個距離100公裡的緩衝區顯示在地圖上,之後開始查詢緩衝區圖形經過的州(相交),將結果顯示在地圖上。可以單擊每個州查看詳細資料。這裡假設你已學習了前幾節的內容,只討論Task用法的部分。
1、利用所畫的線產生緩衝區。畫線利用的是Draw工具中的Freehand,在這個動作完成後會觸發Draw的OnDrawCompleted事件,自然可以在這裡開始進行緩衝區的工作,用的是Geometry Service裡的Buffer。
初始化Geometry Service。假設已經在Map1中添加了ID為glayerResult的GraphicsLayer,linesymbolred是提前設定好的CartographicLineSymbol:
- private void Draw1_OnDrawComplete(object sender, DrawEventArgs args)
- {
- Draw1.Deactivate();//Freehand動作失效
- //將Freehand畫的曲線顯示在地圖上
- GraphicsLayer glayer = Map1.Layers["glayerResult"] as GraphicsLayer;
- Graphic g = new Graphic();
- g.Symbol = linesymbolred;
- g.Geometry = args.Geometry;
- glayer.Graphics.Add(g);
- //初始化Geometry Service
- GeometryService geometrytask = new GeometryService("http://sampleserver1.arcgisonline.com/ArcGIS/rest/services/Geometry/GeometryServer");
- }
複製代碼
GeometryService的初始化使用建構函式來完成的,裡面接受一個URL,這個是Geometry Service的REST APIEndpoint。順便說一下,不同於其他服務比如MapService,一個GISServer只能發布一個GeometryService,並且它的名稱必須是Geometry。
當一個Task完成時會觸發Completed事件,失敗時也有Failed事件,對這兩個事件進行監聽:
- geometrytask.BufferCompleted += new EventHandler(geometrytask_BufferCompleted);
- geometrytask.Failed += new EventHandler(geometrytask_Failed);
複製代碼
設定Buffer操作所需的參數:
- BufferParameters bufferparameters = new BufferParameters();
- bufferparameters.Unit = LinearUnit.Kilometer;
- //必須指定下面兩個spatialreference,否則buffer結果集為空白
- bufferparameters.BufferSpatialReference = new SpatialReference(3395);
- bufferparameters.OutSpatialReference = Map1.SpatialReference;
- bufferparameters.Distances.Add(100);
- bufferparameters.Features.Add(g);
複製代碼
BufferParameters是專門用於Buffer的參數;BufferSpatialReference是將要Buffer的圖形重新投影到這個座標系下(常常需要根據地圖資料所在地方的情況來設定這個參數),並設定Buffer距離的單位為公裡,Buffer的輸出一般與地圖座標系一致;Buffer參數有一個Features屬性,是List類型,裡面的Graphic都將被Buffer。下來將Buffer的任務提交到伺服器(可以看出為什麼這些動作要叫Task):
- geometrytask.BufferAsync(bufferparameters);
複製代碼
以上代碼都放在Draw1_OnDrawComplete函數中。任務提交到伺服器後,由GeometryService接管,計算,完成後會立刻將結果返回給我們,通知我們結果已經完成的方式就是前面綁定的Completed事件。接收到結果後,首先將緩衝區顯示出來:
- private void geometrytask_BufferCompleted(object sender, GraphicsEventArgs args)
- {
- if (args.Results.Count>0)
- {
- GraphicsLayer glayer = Map1.Layers["glayerResult"] as GraphicsLayer;
- Graphic g = new Graphic();
- g.Symbol = fillsymbolBuffer;
- g.Geometry = args.Results[0].Geometry;
- glayer.Graphics.Add(g);
- }
- }
複製代碼
2、利用產生緩衝區的緩衝區進行空間查詢。要達到我們的目的,就還需要進行一個Query的Task,那麼就可以在這裡馬不停蹄的開始Query的Task。步驟基本都是一樣的,初始化,設定參數,提交結果,處理結果:
- private void geometrytask_BufferCompleted(object sender, GraphicsEventArgs args)
- {
- if (args.Results.Count>0)
- {
- GraphicsLayer glayer = Map1.Layers["glayerResult"] as GraphicsLayer;
- Graphic g = new Graphic();
- g.Symbol = fillsymbolBuffer;
- g.Geometry = args.Results[0].Geometry;
- glayer.Graphics.Add(g);
- //初始化QueryTask
- QueryTask querytask = new QueryTask("http://sampleserver1.arcgisonline.com/ArcGIS/rest/services/Demographics/ESRI_Census_USA/MapServer/5");
- //準備接收結果或者處理失敗的通知
- querytask.ExecuteCompleted += new EventHandler(querytask_ExecuteCompleted);
- querytask.Failed += new EventHandler(querytask_Failed);
- //設定Query Task所需的參數
- Query query = new Query();
- query.OutFields.Add("*");//也順便設定了query.ReturnGeometry=true;
- query.Geometry = g.Geometry;
- query.SpatialRelationship = SpatialRelationship.esriSpatialRelIntersects;
- //向伺服器上的對應圖層提交任務
- querytask.ExecuteAsync(query);
- Map1.Cursor = System.Windows.Input.Cursors.Wait;
- }
- }
複製代碼
這裡的查詢實在美國州的圖層上進行的,詳細資料將QueryTask建構函式裡的那個參數輸入瀏覽器查看;query.Geometry是設定需要進行空間查詢的圖形,就是上面緩衝區的結果;OutFields是查詢結果需要返回的欄位,這裡返回全部欄位,如果返回全部欄位,則強制設定了ReturnGeometry為true,如果我們不需要處理結果的圖形資訊,則可以將這個參數設為false,以節省流量,顯然這裡不是;空間關係可以參考API,與Engine中的完全一致。
接下來處理QueryTask完成後的結果:
- private void querytask_ExecuteCompleted(object sender, QueryEventArgs args)
- {
- GraphicsLayer graphicslayer = Map1.Layers["glayerResult"] as GraphicsLayer;
- FeatureSet featureset = args.FeatureSet;
- if (featureset != null && featureset.Features.Count > 0)
- {
- graphicslayer.ClearGraphics();
- listboxResults.Items.Clear();
- foreach (Graphic graphic in featureset.Features)
- {
- graphic.Symbol = fillsymbolresult;
- graphicslayer.Graphics.Add(graphic);
- }
- }
- MyMapTip.GraphicsLayer = graphicslayer;
- Map1.Cursor = System.Windows.Input.Cursors.Arrow;
- }
複製代碼
上面處理空間查詢的結果只是將圖形顯示了出來,那麼對於單擊某個州後,顯示出其詳細資料該怎麼辦呢?從圖一可以看出,用到了Silverlight的DataGrid控制項,資訊從哪裡去呢?記得上面我們設定結果中返回的全部屬性欄位嗎?它們儲存在每個Graphic的Attributes屬性中。要麼綁定到DataGrid裡,要麼一條條添加……你可能已經發現了這條語句MyMapTip.GraphicsLayer =graphicslayer;,還記得第三節的Widgets嗎?那裡我們落下了MapTip這個小傢伙,現在派上用場了。除了在這裡設定MapTip的GraphicsLayer屬性外,在xaml中有如下的定義:
- <esriWidgets:MapTip x:Name="MyMapTip" BorderBrush="#99000000"
- BorderThickness="1" Title="詳細資料" VerticalOffset="10"
- HorizontalOffset="10" Background="#DDFFFFFF" />
複製代碼
僅此而已。MapTip會自動找尋自己GraphicsLayer中的Graphic,當滑鼠移至上方在某個Grpahic上時,會自動讀取它的Attributes屬性並顯示,小玩具又發揮了大作用。
別忘了萬一處理任務失敗時的提示:
- private void geometrytask_Failed(object sender, TaskFailedEventArgs args)
- {
- MessageBox.Show("Buffer Error:" + args.Error);
- }
- private void querytask_Failed(object sender, TaskFailedEventArgs args)
- {
- MessageBox.Show("Query failed: " + args.Error);
- Map1.Cursor = System.Windows.Input.Cursors.Arrow;
- GraphicsLayer graphicslayer = Map1.Layers["glayerResult"] as GraphicsLayer;
- graphicslayer.ClearGraphics();
- }
複製代碼
本節內容完畢。上面講的相對簡略,要理解各個Task和參數的用法,還是需要熟悉Silverlight API和前面提到的REST API。另外,Geoprocessing Service實際上是最強大Task,如果有自己的GISServer,完全可以在上面發布自製的Model或者Python指令碼,以完成各種GIS分析任務,簡單的線上編輯也是可能的。它的用法也萬變不離其宗:初始化,設定參數,提交任務,處理結果。不同的是GeoprocessingService有兩種提交任務的方法:同步和非同步。前者伺服器端處理完任務後會立即將結果發送回用戶端;後者將任務提交後會得到伺服器端返回的一個JobID,即使任務處理完成也不會立即返回,而是需要你拿這個JobID去詢問伺服器:完成了嗎?完成了嗎?完成了嗎?如果完成,則可以取回相應的結果。
前面說到,雖然去外面吃餃子很方便,但是畢竟那是人家做好的,對於老饕來說還需要自己的口感,自己下廚畢竟能控制整個過程的方方面面,哪怕你想做出餃立方也都是有可能的。同樣,ADF編程可以調用伺服器端的ArcObjects,讓你為所欲為,這點是用戶端API無論如何也辦不到的。
下一節我們來對Silverlight API中的圖層類型做一個小結。