如果您是ArcGIS Engine開發人員,也許會有這樣的困惑:爲什麼對兩個要素圖層進行空間選擇,ArcMap中瞬間就出結果了,而Engine中則慢很多倍,尤其是當數據量大時,該速率甚至無法忍受。圖層間如何實現高效的空間選擇呢?相信閱讀完下面的文章,答案會迎刃而解。
下面就帶着問題來開始今天的討論吧。
問題:
假如有一個居民點數據和一個建築物數據,想要知道哪些居民點被建築物所覆蓋,如何實現?
答案:
ArcMap中如何實現?
ArcMap中實現此功能很簡單,即使用菜單條上的Select By Location或者Select Layer By Location工具,數據(點要素類中含有600個點,面要素類中含有750個面)和結果如下圖所示。
用時: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