Skip to content

This demo provides several methods on how to spatial select between two layers in ArcGIS Engine application.

Notifications You must be signed in to change notification settings

xinying180/QueryByLayer

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

9 Commits
 
 
 
 
 
 
 
 

Repository files navigation

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。

Demo

使用ArcGIS Engine 10.5,Visual Studio 2015编写,创建了MapControlApplication模版工程,然后添加菜单栏,分别使用了上面提到的方法进行测试。界面为:

程序界面

*Tips:*测试时是直接加载的已有mxd,然后获取的图层,如果直接从数据源获取要素类,然后创建图层,再查询会相对慢些。

工程下载地址:

QueryByLayer

About

This demo provides several methods on how to spatial select between two layers in ArcGIS Engine application.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages