API Exegesis (or, how to use IBasicGeoprocessor's useSelectedInput argument)
So I'm programming away, innocently trusting the reference material for ESRI ArcObject's IBasicGeoprocessor.Intersect function. The function claims it will intersect two feature classes, and honor a selection set on them. Because our big ESRI application stores data in extremely large SDE feature classes (millions of features) but typically does intersect operations on only a few hundred at a time, we have had to resort to tricks to get intersection performance acceptable. Typically, we script an export of appropriate data to a local geodatabase and perform intersections there. This is unfortunate, because it involves a lot of disk traffic for something that could theoretically be pushed down to SDE, or at least done with the right feature cursors.
It was this afternoon that I noticed the useSelectedInput and useSelectedOverlay arguments to the Intersect function. Selection of the appropriate subset is typically quite fast, so perhaps this was the answer I was looking for. The arguments are boolean, which begs the question: which selection set will they use?
The official developer help notes "The useSelectedInput state refers to whether or not a selected subset of the InputTable is to be intersected. True signifies that a selected subset will be intersected. False signifies that intersect operation will ignore any selected subset.". It's a boolean, so unless ESRI is willing to magically reach into your mind and figure out where on the local stack you stored your favorite ISelectionSet pointer, this won't do you any good.This is when I noticed a funny disconnect between the official description of the argument, and the commentary for the interface as a whole. Like monks in the middle ages copying manuscripts, I wondered who was right? Was Mary a virgin or a young woman?. The summary description said "[i]ntersect clips the features of a line or polygon layer". So who was right? Did the thing intersect feature classes (abstract collections of shapes) or layers (pieces of an actual map).
In the name of abstraction, the arguments to specify the feature classes to be intersected were ITable. Not only is that no help, but it's silly: ITables don't even have shapes. It should be IFeatureClass or IFeatureLayer. It turns out you have to pass the function an IFeatureLayer because those carry implicit selection sets. If you pass the function an IFeatureClass it will work, but there is no way to specify a selection set.
This is wrong on many levels. They should have:
- Provided two overloads of the function. One with IFeatureClass and ISelectionSet arguments, and one with IFeatureLayer and boolean arguments.
- Or, raised a meaningful error message if the function was called with an IFeatureClass and true.
As it turns out, it was all for nought. Even though I was able to successfully talk the code into honoring my selection sets, the intersect was too slow. It appears that all it does is do the intersection first, without the selection sets, then filters the result. In this case, it tries to intersect about 400,000 polygons with 280,000 others. That's probably slower than intersecting 288 with 14.
Once again, ESRI, you get points for being brave enough to expose your APIs in such detail. But this is just plain bad who-let-the-intern-write-the-function programming. Better luck next time.
For the record, here's how you do it.
public bool IntersectPairNoExport(
string sourceFeatureClass, string sourceWhere,
AoWorkspaceContext overlayCtx, string overlayFeatureClass, string overlayWhere,
bool constrainOverlayBySourceSelectionExtent, esriSearchOrder overlaySearchOrder,
AoWorkspaceContext intersectCtx, string intersectFeatureClass)
{
IFeatureClass sourceFc = null, overlayFc = null, intersectFc = null;
IFeatureLayer sourceLayer = null, overlayLayer = null;
IFeatureClassName intersectName = null;
IQueryFilter sourceQf = null, overlayQf = null;
ISelectionSet sourceSel = null, overlaySel = null;
IEnumGeometryBind enumGeometryBind = null;
IGeometryFactory geometryFactory = null;
IGeometry sourceGeometries = null;
IBasicGeoprocessor gp = null;
try
{
// the geoprocessor claims to honor selected subsets. however the only way this works
// is if you pass it feature layers instead of plain tables. feature layers have an implicit
// selection set. how lame is that. In cases where the sets to be intersected are very
// small, this saves us the trouble of copying to a temporary feature class. However, it
// seems to do nothing at all against very large datasets -- it is still necessary to
// pull out selected subsets into smaller datasets and intersect them independently.
sourceFc = OpenFeatureClass(sourceFeatureClass);
sourceQf = new QueryFilter();
sourceQf.WhereClause = sourceWhere;
sourceLayer = new FeatureLayerClass();
sourceLayer.FeatureClass = sourceFc;
((IFeatureSelection) sourceLayer).SelectFeatures(sourceQf, esriSelectionResultEnum.esriSelectionResultNew, false);// esriSelectionType.esriSelectionTypeHybrid, esriSelectionOption.esriSelectionOptionNormal, Workspace);
sourceSel = ((IFeatureSelection) sourceLayer).SelectionSet;
if (sourceSel.Count == 0) return false;
// we want to limit the selection by the envelope of the source fc
// see http://forums.esri.com/Thread.asp?c=93&f=992&t=90612&mc=10 for this fun technique
overlayFc = OpenFeatureClass(overlayFeatureClass);
if (constrainOverlayBySourceSelectionExtent)
{
enumGeometryBind = new EnumFeatureGeometryClass();
enumGeometryBind.BindGeometrySource(sourceQf, sourceSel);
geometryFactory = new GeometryEnvironmentClass();
sourceGeometries = geometryFactory.CreateGeometryFromEnumerator(enumGeometryBind as IEnumGeometry);
overlayQf = NewQueryFilter((ITable)overlayFc, overlayWhere, sourceGeometries.Envelope, overlaySearchOrder);
}
else
{
overlayQf = new QueryFilter(); overlayQf.WhereClause = overlayWhere;
}
overlayLayer = new FeatureLayerClass();
overlayLayer.FeatureClass = overlayFc;
((IFeatureSelection) overlayLayer).SelectFeatures(overlayQf, esriSelectionResultEnum.esriSelectionResultNew, false);// esriSelectionType.esriSelectionTypeHybrid, esriSelectionOption.esriSelectionOptionNormal, overlayCtx.Workspace);
overlaySel = ((IFeatureSelection) overlayLayer).SelectionSet;
if (overlaySel.Count == 0) return false;
intersectName = intersectCtx.NewFeatureClassName(intersectFeatureClass, overlayFc);
gp = new BasicGeoprocessorClass();
intersectFc = gp.Intersect((ITable)sourceLayer, true, (ITable)overlayLayer, true, 0.00002, intersectName);
return true;
}
finally
{
ComUtil.ReleaseCollection(new object[]
{
sourceFc,overlayFc,intersectFc,
sourceLayer, overlayLayer,
intersectName,
sourceQf,overlayQf,
sourceSel,overlaySel,
enumGeometryBind, geometryFactory, sourceGeometries,
gp,
});
}
}

0 Comments:
Post a Comment
Links to this post:
Create a Link
<< Home