ArcGIS Engine實現圖層間空間選擇的優化策略

如果您是ArcGIS Engine開發人員,也許會有這樣的困惑:爲什麼對兩個要素圖層進行空間選擇,ArcMap中瞬間就出結果了,而Engine中則慢很多倍,尤其是當數據量大時,該速率甚至無法忍受。圖層間如何實現高效的空間選擇呢?相信閱讀完下面的文章,答案會迎刃而解。

下面就帶着問題來開始今天的討論吧。

問題:

假如有一個居民點數據和一個建築物數據,想要知道哪些居民點被建築物所覆蓋,如何實現?

答案:

ArcMap中如何實現?

ArcMap中實現此功能很簡單,即使用菜單條上的Select By Location或者Select Layer By Location工具,數據(點要素類中含有600個點,面要素類中含有750個面)和結果如下圖所示。

ArcMap中

用時:0.06秒

ArcGIS Engine中如何實現呢?

ArcGIS Engine中(相信很多用戶是不太喜歡直接調用GP工具的)如何實現該功能呢?一般情況下會考慮使用ISpatialFilter結合遍歷面要素來實現:

ISpatialFilter可以進行空間查詢和屬性查詢。其Geometry屬性傳入要查詢的幾何,僅可使用high-level geometry(如polygon、polyline、points、multipoints)、envelope和geometry bags,這裏使用面要素的geometry;其GeometryField屬性用於指定過濾所使用的幾何字段,這裏使用點要素類的幾何字段pointFeatureClass.ShapeFieldName;其SpatialRel屬性用於指定空間關係,公式爲[query_geometry] [spatial_relationship] [feature],這裏的query_geometry爲面,feature爲點要素,空間關係就應該是包含,所以應該使用esriSpatialRelContains(注意與ArcMap中有所區別,ArcMap中需設置空間關係爲CompletelyWithin)。SubFields可以指定查詢後返回的屬性字段,以逗號分隔。如果不設置的話會默認爲“*”,即返回所有字段。如果需要獲取要素屬性值得話,最好設置該字段,可以提高性能。當然如果不獲取屬性的話,那這個字段可設可不設,比如選擇要素。此外,如果還想同時進行屬性查詢,直接設置WhereClause語句即可。主要代碼:

 

            //從MapControl中獲取的點圖層
            IFeatureLayer pointFeatureLayer = axMapControl1.get_Layer(0) as IFeatureLayer;
            IFeatureSelection pointFeatureSelection = pointFeatureLayer as IFeatureSelection;
            //從MapControl中獲取的面圖層
            IFeatureLayer polygonFeatureLayer = axMapControl1.get_Layer(1) as IFeatureLayer;
            //循環遍歷面要素類內部的面,逐一進行查詢
            IQueryFilter queryFilter = new QueryFilterClass();
            //Search如果返回屬性值的話設置SubFields會提高效率
            queryFilter.SubFields = "Shape";
            IFeatureCursor cursor = polygonFeatureLayer.Search(queryFilter, true);
            IFeature polygonFeature = null;
            while ((polygonFeature = cursor.NextFeature()) != null)
            {
                IGeometry queryGeometry = polygonFeature.Shape;
                //構建空間查詢
                ISpatialFilter spatialFilter = new SpatialFilterClass();
                spatialFilter.Geometry = queryGeometry;
                spatialFilter.GeometryField = pointFeatureLayer.FeatureClass.ShapeFieldName;
                spatialFilter.SpatialRel = esriSpatialRelEnum.esriSpatialRelContains;               
                pointFeatureSelection.SelectFeatures(spatialFilter as IQueryFilter, esriSelectionResultEnum.esriSelectionResultAdd, false);              
            }     
            //釋放遊標          
            System.Runtime.InteropServices.Marshal.FinalReleaseComObject(cursor);

 

用時:1.45秒

怎麼和ArcMap中效率差這麼多啊,別急,下面就來看優化方法。

優化1:創建空間緩存

使用ISpatialCacheManager接口對要素類創建空間緩存,然後使用上面的方法遍歷選擇。如果對同一個範圍進行多次空間查詢的話,先構建空間緩存會提升效率,因爲其降低了數據庫的訪問次數,比如下面場景,當然也很適用於我們的問題。

這裏寫圖片描述

使用方法就是打開進行查詢的要素類,爲該範圍創建空間緩存,執行查詢,最後釋放緩存。主要代碼:

 

            //填充Spatial Cache
            ISpatialCacheManager spatialCacheManager = (ISpatialCacheManager)(pointFeatureLayer as IDataset).Workspace;
            IEnvelope cacheExtent = (pointFeatureLayer as IGeoDataset).Extent;
            //檢測是否存在緩存
            if (!spatialCacheManager.CacheIsFull)
            {
                //不過不存在,則創建緩存
                spatialCacheManager.FillCache(cacheExtent);
            }

            //執行空間查詢操作,與上文一樣
            ...            
            //清空空間緩存
            spatialCacheManager.EmptyCache(); 

 

用時:0.45秒

可見,創建空間緩存後效率有所提升,但與ArcMap中調用GP相比,效率還差了幾乎一個數量級。怎麼辦?

優化2:使用IGeometryBag接口

細心的你也許會發現上述方法之所以慢,是因爲執行了多次SelectFeatures方法,如果只執行一次,肯定會快很多。而我們開始介紹ISpatialFilter的Geometry屬性時提到過,Geometry可以傳入GeometryBag,那麼GeometryBag是什麼呢?GeometryBag是Geometry的集合,可以往裏添加N個Geometry,好吧,既然GeometryBag是個集合,那我們就可以把遍歷的面都添加進去,這樣就可以只執行一次SelectFeatures,很顯然可以提高效率。不過使用GeometryBag時有一點需要注意:必須爲其設置空間參考,因爲添加Geometry(即使原本有空間參考)到GeometryBag中時會丟失空間參考。此外,如果爲該GeometryBag創建空間索引會提高效率。主要代碼:

 

            //構建GeometryBag
            IGeometryBag geometryBag = new GeometryBagClass();
            IGeometryCollection geometryCollection = (IGeometryCollection)geometryBag;
            IGeoDataset geoDataset = (IGeoDataset)polygonFeatureLayer;
            ISpatialReference spatialReference = geoDataset.SpatialReference;
            //一定要給GeometryBag賦空間參考
            geometryBag.SpatialReference = spatialReference;            
            IQueryFilter queryFilter = new QueryFilterClass();
            //Search如果返回屬性值的話設置SubFields會提高效率
            queryFilter.SubFields = "Shape";           
            //遍歷面要素類,逐一獲取Geometry並添加到GeometryBag中
            IFeatureCursor cursor = polygonFeatureLayer.Search(queryFilter, true);
            IFeature polygonFeature = null;
            while ((polygonFeature = cursor.NextFeature()) != null)
            {
                geometryCollection.AddGeometry(polygonFeature.ShapeCopy);
            }
            //爲GeometryBag生成空間索引,以提高效率
            ISpatialIndex spatialIndex = (ISpatialIndex)geometryBag;
            spatialIndex.AllowIndexing = true;
            spatialIndex.Invalidate();
            //構建空間查詢
            ISpatialFilter spatialFilter = new SpatialFilterClass();
            spatialFilter.Geometry = geometryBag;
            spatialFilter.GeometryField = pointFeatureLayer.FeatureClass.ShapeFieldName;
            spatialFilter.SpatialRel = esriSpatialRelEnum.esriSpatialRelContains;
            //選擇的話可以不設置SubFields
            pointFeatureSelection.SelectFeatures(spatialFilter as IQueryFilter, esriSelectionResultEnum.esriSelectionResultAdd, false);

 

用時:0.045秒

喜大普奔!該方法居然比ArcMap中直接調用GP還要快?!可是我覺得代碼稍顯複雜,有沒有更簡化而且高效的方法呢?(居然還想要自行車…)

優化3:使用IQueryByLayer接口

IQueryByLayer接口就是用來進行圖層間空間選擇的!!!其FromLayer屬性是指對哪個圖層進行選擇,這裏即爲點圖層;其ByLayer屬性是指查詢幾何所在的圖層,即面圖層;其LayerSelectionMethod就是兩個圖層間的空間關係,這裏是esriLayerSelectCompletelyWithin(細心的同學注意到了吧,空間關係和ArcMap中使用的一模一樣,ArcMap甚有可能就是用的該接口);此外還有一個UseSelectedFeatures屬性很重要,我開始沒有設置,結果導致程序一直報下面的錯誤:

報錯

該屬性是說ByLayer中是否進行了選擇,如果面圖層中沒有選中要素的話,一定要設置爲該屬性爲false,如果設爲true或者不設置都會報這個錯。但是,如果面圖層中進行了選中,想用選中的面進行空間查詢,就要將其設爲true了,如果設爲false是按整個面要素類進行查詢的,如果不設置是按選中的面進行查詢的,所以我認爲該屬性的默認值爲true,但AO幫助中並沒有說明。主要代碼:

 

            //從MapControl中獲取的點圖層
            IFeatureLayer pointFeatureLayer = axMapControl1.get_Layer(0) as IFeatureLayer;
            IFeatureSelection pointFeatureSelection = pointFeatureLayer as IFeatureSelection;
            //從MapControl中獲取的面圖層
            IFeatureLayer polygonFeatureLayer = axMapControl1.get_Layer(1) as IFeatureLayer;
            //構建QueryByLayer
            IQueryByLayer queryByLayer = new QueryByLayerClass();
            queryByLayer.FromLayer = pointFeatureLayer;
            queryByLayer.ByLayer = polygonFeatureLayer;
            queryByLayer.LayerSelectionMethod = esriLayerSelectionMethod.esriLayerSelectCompletelyWithin;
            //該參數需要設置
            queryByLayer.UseSelectedFeatures = false;
            ISelectionSet selectionSet = queryByLayer.Select();
            pointFeatureSelection.SelectionSet = selectionSet;
            axMapControl1.Refresh();

 

用時:0.038秒

這種方法的代碼比上面那種簡單的多,而且用時也更少,這就是我認爲既簡單又高效的方法。由上可見,ArcGIS Engine中進行圖層間的空間選擇,方法使用正確了,確實會與ArcMap效率相當,甚至還要更快哦!

總結一下:

本文僅以圖層間的空間選擇爲例進行了優化,其實在進行空間查詢時也可以採用文中的思想,比如:

1, 對同一區域進行多次查詢時可以使用ISpatialCacheManager創建空間緩存; 
2, 要使用多個geometry進行空間查詢時,使用GeometryBag會提高效率; 
3, 圖層間的查詢也是可以轉化爲空間選擇的,使用IQueryByLayer接口獲取ISelectionSet,進而獲取到所有的要素; 
4, 多次使用IRelationalOperator或者ITopologicalOperator接口進行空間關係的判斷時也可以使用GeometryBag,但具體需要看所使用的方法是否支持GeometryBag。

 

原文地址:http://blog.csdn.net/xinying180/article/details/60580881

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