Bad buffer geometry throwing COMException in SelectFeatures()

1775
2
01-06-2011 07:15 AM
by Anonymous User
Not applicable
Original User: ramgarden

Here's the deal:
We are trying to select all the poles that are within a given distance from the primary conductors of a given FEEDERID.
The way we do it and has worked just fine so far is:
1. Get all the primary conductor features with the given FEEDERID and put them in a geometrybag.
2. Union all the conductor features together into one giant polyline.
3. Buffer this giant polyline with the given distance (usually 100 ft).
4. Use this buffer in a spatial filter given to the IFeatureSelection.SelectFeatures() method.
This works 99% of the time perfectly just as it should. BUT for CERTAIN CIRCUITS it creates the buffer in strange, malformed ways
and when trying to use the buffer in the SelectFeatures() method it throws a COMException with this error code:
-2147215968.

After some searching it looks like this error code has to do with overlapping polygons or bad geometry in general.
Even after calling Simplify() on the buffer polygon after setting the IsKnownSimple_2 = false it still throws this error.

There is even a weird thing where if we use 100 for the buffer size it makes a small buffer around one or two of the lines instead of all of them, but if we subtract 1 from the size and try again over and over it will eventually get to something like 91 or some magic distance and is different for each of these problem FEEDERIDs and it will create the buffer just fine.  But even then it still throws the COMException when trying to select with it.

Is there something I need to do to better check and fix the geometry of the primary conductors and/or the buffer it creates?

Here is a screenshot of when it creates two small buffers instead of one big one around the whole thing (buffersize = 100): Attachment BadBufferZoomedIn.png

Here's a shot of when it creates the buffer correctly around the whole circuit: Attachment GoodBufferZoomedIn.png

Here's the code that creates the buffer:
private IPolygon BufferGeometryBag(double bufferDistance, IGeometryBag sourceGeometryBag)
        {
            //QI to IGeometryCollection
            IGeometryCollection geometryCollection = (IGeometryCollection)sourceGeometryBag;
            
            ITopologicalOperator2 pITopologicalOperator_Polygon = new Polygon() as ITopologicalOperator2;
            ITopologicalOperator topologicalOperator = sourceGeometryBag as ITopologicalOperator;
            IPolygon returnBuffer;

            //Construct a union of all polylines
            ITopologicalOperator2 topoOp_Polyline = new Polyline() as ITopologicalOperator2;
            topoOp_Polyline.ConstructUnion(sourceGeometryBag as IEnumGeometry);
            
            //simplify the polyline.
            topoOp_Polyline.IsKnownSimple_2 = false;
            topoOp_Polyline.Simplify();
            
            //buffer the polyline.  
            returnBuffer = topoOp_Polyline.Buffer(bufferDistance) as IPolygon;
            returnBuffer.SpatialReference = _SpatialReference;
            ITopologicalOperator2 topo2 = returnBuffer as ITopologicalOperator2;
            topo2.IsKnownSimple_2 = false;
            topo2.Simplify();
            returnBuffer = topo2 as IPolygon;
            returnBuffer.SimplifyPreserveFromTo();
            
            _Log4Net.Debug("conductor envelope width: " + _ConductorGeometries.Envelope.Width +
                    " height: " + _ConductorGeometries.Envelope.Height);
            _Log4Net.Debug("   buffer envelope width: " + returnBuffer.Envelope.Width +
                    " height: " + returnBuffer.Envelope.Height);

            //the buffer's envelope should always be twice the buffer distance greater than the
            // conductors' envelope. So if buffer distance is 100 then the buffer envelope's
            // width should be 200 feet greater than the conductors' envelope's width.
            // The height should also be 200 feet greater.
            while ( (returnBuffer.Envelope.Height < (_ConductorGeometries.Envelope.Height + (bufferDistance * 2))) &&
                    (returnBuffer.Envelope.Width < (_ConductorGeometries.Envelope.Width + (bufferDistance * 2))) )
            {
                bufferDistance -= 1; //subtract 1
                _Log4Net.Debug("Non-circuit buffer malformed. Trying again with bufferdistance: " + bufferDistance);
                returnBuffer = topoOp_Polyline.Buffer(bufferDistance) as IPolygon;
                returnBuffer.SpatialReference = _SpatialReference;
                topo2 = returnBuffer as ITopologicalOperator2;
                topo2.IsKnownSimple_2 = false;
                topo2.Simplify();
                returnBuffer = topo2 as IPolygon;
                returnBuffer.SimplifyPreserveFromTo();
                _Log4Net.Debug("conductor envelope width: " + _ConductorGeometries.Envelope.Width +
                    " height: " + _ConductorGeometries.Envelope.Height);
                _Log4Net.Debug("   buffer envelope width: " + returnBuffer.Envelope.Width +
                        " height: " + returnBuffer.Envelope.Height);
            }
            return returnBuffer;
   }


Here's the code that selects all the poles (in my DB the pole layer is the only one with the buffer print model name):

public void MakeBufferedFeatureLayers(IPolygon bufferPoly, string bufferPrintModelName, IWorkspace circuitWorkspace, 
            MXDManager mxdMan, FeederFeatureManager ffManager)
        {
            ISet bufferFeatLayerSet;

            bufferFeatLayerSet = GetLayersByModelName(bufferPrintModelName, mxdMan);
                        
            //ITable tempLayerTable;
            IFeatureClass tempLayerClass;
            ISpatialFilter bufferSpatialFilter;
            ISelectionSet featSelectionSet;

            if (bufferFeatLayerSet != null)
            {
                //for each layer, select all of the features that are within the buffer polygon
                bufferFeatLayerSet.Reset();
                IFeatureLayer bufferedFeaturesLayer = bufferFeatLayerSet.Next() as IFeatureLayer;
                while (bufferedFeaturesLayer != null)
                {
                    //select the features within the buffer
                    bufferSpatialFilter = new SpatialFilterClass();
                    bufferSpatialFilter.Geometry = bufferPoly as IGeometry;
                    bufferSpatialFilter.SpatialRel = esriSpatialRelEnum.esriSpatialRelContains;

                    try
                    {
                        IFeatureSelection featureSelection = bufferedFeaturesLayer as IFeatureSelection;
                        featureSelection.SelectFeatures(bufferSpatialFilter, esriSelectionResultEnum.esriSelectionResultNew, false);
                        featSelectionSet = featureSelection.SelectionSet;
                    }
                    catch(COMException ce)
                    {
                        //throw new Exception("Couldn't select non-circuit features using buffer. Try reducing or increasing non-circuit buffer size configuration setting.");
                        _Log4Net.Error("Couldn't select non-circuit features using buffer. Try reducing or increasing non-circuit buffer size configuration setting.");
                        return;
                    }

                    _Log4Net.Debug("selected " + featSelectionSet.Count + " features within the buffer.");

                    //then create a new layer out of these selected features.
                    mxdMan.CreateTemporaryLayerFromSelected(bufferedFeaturesLayer);

                    _Log4Net.Debug("created temporary layer OK");

                    //next layer with buffer print model name
                    bufferedFeaturesLayer = bufferFeatLayerSet.Next() as IFeatureLayer;
                }
                   
            }
            else
            {
                _Log4Net.Debug("Couldn't find any non-circuit feature classes with the " + bufferPrintModelName + " model name.");
            }


        }
0 Kudos
2 Replies
by Anonymous User
Not applicable
Original User: ramgarden

Forgot to mention this is using ArcGIS Desktop 9.2 Service Pack 5 ArcObjects.
0 Kudos
JoshPritt
New Contributor III
Here's the workaround:
Instead of trying to create the buffer myself and then using it as the spatial filter geometry, I stumbled upon the IQueryByLayer interface.  This lets you assign two layers, the layer you select BY (the lines in my case) and the layer you select FROM (the poles in my case).  It also has a buffer distance property and a flag to USE SELECTED (I select the power lines in the circuit I'm interested in first in the BY layer).  Then just call the one method it has, Select(), and it returns a set of the poles that are within the distance from the selected lines!  BINGO!
Also in my case I loop through multiple line layers since the client wanted to use both overhead and underground cables to find the whole circuit. I combine the set of poles selected by both the OH and UG lines in order to finally show them all.
I also believe the COMException occurs since I'm trying to end up with a giant, single buffer polygon around all the lines by either 1. buffer each line feature then union the buffers into one, or 2. union all the lines into one giant, complex line then buffer that.  I think it would work just fine if I were to leave all the buffer polygons separate and use that multipolygon polygon as the spatial filter but I'm going to leave the IQueryByLayer in place since it works at any buffer distance and is plenty fast.
Here's the code:
/// <summary>
        /// Returns a selection set of features from the given nonCircuitLayer (such as Pole)
        /// that is within the buffer distance stored in the given config manager.
        /// Uses IQueryByLayer to accomplish this.
        /// </summary>
        /// <param name="confMan"></param>
        /// <param name="nonCircuitLayer"></param>
        /// <param name="mxdMan"></param>
        /// <returns></returns>
        public ISelectionSet SelectByLocation(ConfigManager confMan, ref IFeatureLayer nonCircuitLayer, MXDManager mxdMan,
                    CircuitToPlotManager ctpMan)
        {
            ISelectionSet returnSet = null;
            Hashtable duplicateTable = new Hashtable(); //keeps track of layers we've already used
            IQueryByLayer pQueryByLayer = new QueryByLayerClass();
            IFeatureSelection circuitLayerSelection = null;

            //loop through each circuit layer (usually Pri OH then Pri UG)
            ArrayList circuitFeatureLayerList = mxdMan.GetFeatureLayersByModelName(confMan.CIRCUITFEATUREMODELNAME);
            foreach (object circuitLayerObj in circuitFeatureLayerList)
            {
                //get the feature layer
                IFeatureLayer circuitLayer = (IFeatureLayer)circuitLayerObj;
                //make sure we haven't already looked in this layer
                if (!duplicateTable.Contains(circuitLayer.FeatureClass.AliasName))
                {
                    duplicateTable.Add(circuitLayer.FeatureClass.AliasName, circuitLayer);
                    //select the features in this layer with the current feederID.
                    string circuitID = ctpMan.CircuitNumber;
                    IQueryFilter pQueryFilter = new QueryFilterClass();
                    pQueryFilter.SubFields = "*";

                    IMMEnumField fieldEnum = getFieldNameEnum(circuitLayer.FeatureClass, confMan.FEEDERIDFIELDMODELNAME);
                    if (fieldEnum != null)
                    {
                        IField feederField = fieldEnum.Next();
                        if (feederField != null)
                        {
                             string feederFieldName = feederField.Name;

                            //set up the where clause to use this feature classes feeder id field.
                            //Do all this field stuff just in case each feature class has a different
                            //field for each to save the feeder id (or circuit name).
                            pQueryFilter.WhereClause = feederFieldName + " = '" + circuitID + "'";
                            circuitLayerSelection = circuitLayer as IFeatureSelection;
                            circuitLayerSelection.SelectFeatures(pQueryFilter, esriSelectionResultEnum.esriSelectionResultNew, false);

                            //querybylayer using the given parameters.
                            //set up the search parameters
                            pQueryByLayer.LayerSelectionMethod = esriLayerSelectionMethod.esriLayerSelectWithinADistance;
                            pQueryByLayer.BufferDistance = confMan.NonCircuitBufferSize;
                            pQueryByLayer.BufferUnits = esriUnits.esriFeet;
                            pQueryByLayer.UseSelectedFeatures = true;
                            pQueryByLayer.ResultType = esriSelectionResultEnum.esriSelectionResultAdd;
                            pQueryByLayer.ByLayer = circuitLayerSelection as IFeatureLayer;
                            pQueryByLayer.FromLayer = nonCircuitLayer;
                            ISelectionSet tempSet = pQueryByLayer.Select();
                            if (returnSet == null)
                            {
                                returnSet = tempSet;
                            }
                            else
                            {
                                returnSet.Combine(tempSet, esriSetOperation.esriSetUnion, out returnSet);
                            }
                        }
                    }                    

                }//end if duplicatetable...
                
            }//next circuit layer

            if (returnSet != null)
            {
                _Log4Net.Debug("SelectByLocation return set contains " + returnSet.Count + " items.");
            }
            
            return returnSet;

        }//end method
0 Kudos