Take the 2-minute tour ×
Geographic Information Systems Stack Exchange is a question and answer site for cartographers, geographers and GIS professionals. It's 100% free, no registration required.

I am updating my addins from 10.0 to 10.1 and my code is crashing on the IPolygon2.QueryExteriorRingsEx Method.

I understand there is something strange going on with this method because Developer Help says it does not work in C# .Net (See remarks: "Does not work in .NET. Use Polygon4.get_InteriorRingBag instead.") Under remarks it also states that this method only accepts an array of type IRing for exteriorRings. Using an array of IRing2 won't work. I am using iRing.

All worked great in 10.0 but the method now crashes if I attempt to query more that a single exterior ring. (Querying a polygon with one exterior ring passes)

Can someone confirm this behaviour before I attempt to re-write this procedure using the Polygon4.get_InteriorRingBag method instead?

Procedure below - the problem line is inside the do while loop

Public Sub PolygonsToPolylines(ByVal sourceFeatureLayer As ESRI.ArcGIS.Carto.IFeatureLayer, ByVal destFeatureLayer As ESRI.ArcGIS.Carto.IFeatureLayer)
On Error GoTo trap

    Dim MxApplication As ESRI.ArcGIS.ArcMapUI.IMxApplication = TryCast(My.ArcMap.Application, ESRI.ArcGIS.ArcMapUI.IMxApplication)

    Dim pEditor As ESRI.ArcGIS.Editor.IEditor2
    Dim pEditLayers As ESRI.ArcGIS.Editor.IEditLayers
    Dim pActiveView As ESRI.ArcGIS.Carto.IActiveView
    Dim pEnumFeature As ESRI.ArcGIS.Geodatabase.IEnumFeature
    Dim pFeature As ESRI.ArcGIS.Geodatabase.IFeature
    Dim pPolygon As ESRI.ArcGIS.Geometry.IPolygon2
    Dim pTempPolyline As ESRI.ArcGIS.Geometry.ITopologicalOperator
    Dim pNewFeature As ESRI.ArcGIS.Geodatabase.IFeature
    Dim pPolygon2 As ESRI.ArcGIS.Geometry.IPolygon2
    Dim pPolyline As ESRI.ArcGIS.Geometry.IPolyline
    Dim pPolylinePointColl As ESRI.ArcGIS.Geometry.IPointCollection
    Dim pRings() As ESRI.ArcGIS.Geometry.IRing
    Dim pRing As ESRI.ArcGIS.Geometry.IRing
    Dim pRings2() As ESRI.ArcGIS.Geometry.IRing
    Dim pRing2 As ESRI.ArcGIS.Geometry.IRing

    'new
    Dim pFeatureSelection As ESRI.ArcGIS.Carto.IFeatureSelection
    Dim pSelectionSet As ESRI.ArcGIS.Geodatabase.ISelectionSet
    Dim pFeatureCursor As ESRI.ArcGIS.Geodatabase.IFeatureCursor = Nothing

    Dim r As Long, i2 As Long, r2 As Long, c As Long, c2 As Long
    Dim d As Long 'progress dialog counter

    Dim source3D As Boolean, dest3D As Boolean

    Has_3D_value(sourceFeatureLayer.FeatureClass, source3D) 'determine whether source features class is 3D
    Has_3D_value(destFeatureLayer.FeatureClass, dest3D) 'determine whether destination features class is 3D


    'Get a handle to the Editor extension
    pEditor = GetEditorFromArcMap(MxApplication)

    If pEditor Is Nothing Then Exit Sub

    pEditLayers = pEditor 'QI
    pActiveView = pEditor.Map

    If pEditor.EditState = ESRI.ArcGIS.Editor.esriEditState.esriStateNotEditing Then
        MsgBox("You must be editing to use this feature!", MsgBoxStyle.Information)
       Exit Sub
    End If

    'QUERY Layer selection to get cursor'''''''''''''''''''''''''''''''''''''''''''''''
    pFeatureSelection = sourceFeatureLayer

    pSelectionSet = pFeatureSelection.SelectionSet
    d = pSelectionSet.Count
    If d < 1 Then
        MsgBox("You must select at least 1 feature!") : Exit Sub
    End If

    'retreive selection into a cursor
    pSelectionSet.Search(Nothing, False, pFeatureCursor)
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

    'Set up Progress Bar''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

    'Create a CancelTracker
    'Dim trackCancel As ESRI.ArcGIS.esriSystem.ITrackCancel = New ESRI.ArcGIS.Display.CancelTrackerClass '10.0 
    Dim trackCancel As ESRI.ArcGIS.esriSystem.ITrackCancel = New ESRI.ArcGIS.Display.CancelTracker '10.1 update

    Dim progressDialogFactory As ESRI.ArcGIS.Framework.IProgressDialogFactory = New ESRI.ArcGIS.Framework.ProgressDialogFactoryClass

    'Set the properties of the Step Progressor
    Dim int32_hWnd As System.Int32 = My.ArcMap.Application.hWnd
    Dim stepProgressor As ESRI.ArcGIS.esriSystem.IStepProgressor = progressDialogFactory.Create(trackCancel, int32_hWnd)
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

    'Set the properties of the Step Progressor
    stepProgressor.MinRange = 0
    stepProgressor.MaxRange = d
    stepProgressor.StepValue = 1
    stepProgressor.Message = "Copying..."

    'Create the ProgressDialog. This automatically displays the dialog
    Dim progressDialog2 As ESRI.ArcGIS.Framework.IProgressDialog2 = CType(stepProgressor, ESRI.ArcGIS.Framework.IProgressDialog2) ' Explict Cast

    ' Set the properties of the ProgressDialog
    progressDialog2.CancelEnabled = True
    progressDialog2.Description = "Copy Features"
    progressDialog2.Title = "Copying..."
    progressDialog2.Animation = ESRI.ArcGIS.Framework.esriProgressAnimationTypes.esriDownloadFile
    ' Step. Do your big process here.
    Dim boolean_Continue As System.Boolean
    boolean_Continue = True
    Dim i As System.Int32
    '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

    'Start an edit operation
    pEditor.StartOperation()

    pFeature = pFeatureCursor.NextFeature

    Do While Not pFeature Is Nothing

        'Progress Bar''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
        Dim statusBar As ESRI.ArcGIS.esriSystem.IStatusBar = TryCast(My.ArcMap.Application.StatusBar, ESRI.ArcGIS.esriSystem.IStatusBar)
        statusBar.Message(0) = i.ToString
        '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

        If pFeature.Shape.GeometryType = ESRI.ArcGIS.Geometry.esriGeometryType.esriGeometryPolygon Then
            c = c + 1
            pPolygon2 = pFeature.ShapeCopy
            SimplifyGeometry(pPolygon2)

            r = pPolygon2.ExteriorRingCount
            If r > 0 Then
                 r = r - 1
                ReDim pRings(r)
                pPolygon2.QueryExteriorRingsEx(pPolygon2.ExteriorRingCount, pRings(0))

                For i = 0 To r
                    pRing = pRings(i)

                    r2 = pPolygon2.InteriorRingCount(pRing)

                    If r2 > 0 Then
                        r2 = r2 - 1
                        ReDim pRings2(r2)
                        pPolygon2.QueryInteriorRingsEx(pRing, pPolygon2.InteriorRingCount(pRing), pRings2(0))
                        For i2 = 0 To r2
                            pRing2 = pRings2(i2)

                            pPolylinePointColl = New ESRI.ArcGIS.Geometry.Polyline
                            pPolylinePointColl.AddPointCollection(pRing2)
                            pPolyline = pPolylinePointColl 'QI
                            SimplifyGeometry(pPolyline)

                            SimplifyGeometry(pPolyline)
                            pNewFeature = destFeatureLayer.FeatureClass.CreateFeature
                            pNewFeature.Shape = pPolyline
                            CopyFieldValues(pFeature, pNewFeature) 'copy matching field values
                            pNewFeature.Store()
                            c2 = c2 + 1
                        Next
                    End If

                    pPolylinePointColl = New ESRI.ArcGIS.Geometry.Polyline
                    pPolylinePointColl.AddPointCollection(pRing)
                    pPolyline = pPolylinePointColl 'QI
                    SimplifyGeometry(pPolyline)

                    pNewFeature = destFeatureLayer.FeatureClass.CreateFeature

                    If (dest3D = True And source3D = False) Or (dest3D = True And source3D = True) Then
                        SetZAware(pPolyline) 'make geometry zAware
                        'FUTURE UPDATE = COPY or ASSIGN 3D Values to all geometry points
                    End If

                    If dest3D = False And source3D = True Then
                        RemoveZAware(pPolyline) 'remove zAware from geometry
                    End If

                    pNewFeature.Shape = pPolyline
                    CopyFieldValues(pFeature, pNewFeature) 'copy matching field values
                    pNewFeature.Store()
                    c2 = c2 + 1

                    'Check if the cancel button was pressed. If so, stop process
                    boolean_Continue = trackCancel.Continue
                    If Not boolean_Continue Then
                      Exit For
                    End If
                Next
            End If

        End If

        pFeature = pFeatureCursor.NextFeature
    Loop

    'Done
    trackCancel = Nothing
    stepProgressor = Nothing
    progressDialog2.HideDialog()
    progressDialog2 = Nothing

    MsgBox("Succesfully created " & c2 & " lines from " & c & " selected polygons.")

    'Complete the edit operation
    pEditor.StopOperation("Copy Polygons to Polylines")

    'Flag the area of the new feature for refreshing
    pActiveView.Refresh()

    Exit Sub

trap:
    MsgBox(Err.Description)
    pEditor.AbortOperation()

CancelProgressBar:
    'Progress cleanup
    progressDialog2.HideDialog()
    trackCancel = Nothing
    stepProgressor = Nothing
    progressDialog2 = Nothing

End Sub
share|improve this question
    
The link goes to the 9.1 help. The 10.1 help (I didn't check earlier versions) does list that remark. Not sure if this link will work, but... resources.arcgis.com/en/help/arcobjects-net/componenthelp/… –  mkennedy Feb 7 '13 at 21:35
    
Ok thanks i will fix that. –  Jakub Feb 7 '13 at 21:37
add comment

2 Answers 2

up vote 3 down vote accepted

At 10.1 SP1, I am able to reproduce the System.ExecutionEngineException (which is apparently uncatchable) crash if I target .NET 3.5 and I use a polygon that has multiple exterior rings with IPolygon2.QueryExteriorRingsEx. It works fine for single exterior ring polygons.

If I target .NET 4.0 I can use QueryExteriorRingsEx without issue for polygons with multiple exterior rings.

I was able to use IPolygon4.ExteriorRingBag and InteriorRingBag without issue on both framework versions.

I also tested at 10.0 SP5 and found that QueryExteriorRingsEx works fine on both .NET 3.5 and .NET 4.0.

Here is the C# code I tested with (LINQPad versions here (10.1) and here (10.0):

using System;
using System.Collections.Generic;
using System.Linq;
using ESRI.ArcGIS.esriSystem;
using ESRI.ArcGIS.Geometry;

namespace RingTest1
{
    class Program
    {
        [STAThread()]
        static void Main(string[] args)
        {
            if (ESRI.ArcGIS.RuntimeManager.ActiveRuntime == null)
                ESRI.ArcGIS.RuntimeManager.BindLicense(ESRI.ArcGIS.ProductCode.EngineOrDesktop);

            var polygon1 = CreateSinglePartPolygon();
            var polygon2 = CreateDonutPolygon();
            var polygon3 = CreateMultiPartPolygon();
            Console.WriteLine(PolygonRingsToPolylines(polygon1).Count());
            Console.WriteLine(PolygonRingsToPolylines(polygon2).Count());
            Console.WriteLine(PolygonRingsToPolylines(polygon3).Count());
            Console.WriteLine(QueryExteriorRingsExTest(polygon1).Count());
            Console.WriteLine(QueryExteriorRingsExTest(polygon2).Count());
            Console.WriteLine(QueryExteriorRingsExTest(polygon3).Count());
        }

        private static IEnumerable<IRing> QueryExteriorRingsExTest(IPolygon polygon)
        {
            var polygon2 = (IPolygon2)polygon;
            IRing exteriorRings;
            polygon2.QueryExteriorRingsEx(polygon2.ExteriorRingCount, out exteriorRings);
            for (int i = 0; i < polygon2.ExteriorRingCount; i++)
            {
                yield return exteriorRings;
            }
        }

        private static IEnumerable<IPolyline> PolygonRingsToPolylines(IPolygon polygon)
        {
            var polygon4 = (IPolygon4)polygon;
            var exteriorRingGeometryBag = (IGeometryBag)polygon4.ExteriorRingBag;
            var exteriorRingGeometryCollection = (IGeometryCollection)exteriorRingGeometryBag;
            for (int i = 0; i < exteriorRingGeometryCollection.GeometryCount; i++)
            {
                var exteriorRingGeometry = exteriorRingGeometryCollection.get_Geometry(i);
                yield return SegmentCollectionToPolyline((ISegmentCollection)exteriorRingGeometry);

                var interiorRingGeometryBag = polygon4.get_InteriorRingBag((IRing)exteriorRingGeometry);
                var interiorRingGeometryCollection = (IGeometryCollection)interiorRingGeometryBag;
                for (int j = 0; j < interiorRingGeometryCollection.GeometryCount; j++)
                {
                    var interiorRingGeometry = interiorRingGeometryCollection.get_Geometry(i);
                    yield return SegmentCollectionToPolyline((ISegmentCollection)interiorRingGeometry);
                }
            }
        }

        private static IPolyline SegmentCollectionToPolyline(ISegmentCollection segmentCollection)
        {
            var polyline = (IPolyline)new PolylineClass();
            var geomcoll = (IGeometryCollection)polyline;
            var pathcoll = (ISegmentCollection)new PathClass();
            for (int i = 0; i < segmentCollection.SegmentCount; i++)
            {
                var segment = segmentCollection.get_Segment(i);
                pathcoll.AddSegment(segment);
            }
            geomcoll.AddGeometry((IGeometry)pathcoll);
            geomcoll.GeometriesChanged();
            return polyline;
        }

        private static IPolygon CreateSinglePartPolygon()
        {
            var polygon = (IPolygon)new PolygonClass();
            var pointcoll = (IPointCollection4)polygon;
            var geometryBridge2 = (IGeometryBridge2)new GeometryEnvironmentClass();
            var aWKSPointBuffer = new WKSPoint[5];

            // Exterior ring
            aWKSPointBuffer[0].X = 0;
            aWKSPointBuffer[0].Y = 0;
            aWKSPointBuffer[1].X = 0;
            aWKSPointBuffer[1].Y = 1;
            aWKSPointBuffer[2].X = 1;
            aWKSPointBuffer[2].Y = 1;
            aWKSPointBuffer[3].X = 1;
            aWKSPointBuffer[3].Y = 0;
            aWKSPointBuffer[4].X = 0;
            aWKSPointBuffer[4].Y = 0;
            geometryBridge2.AddWKSPoints(pointcoll, ref aWKSPointBuffer);

            var topoOp = (ITopologicalOperator2)polygon;
            topoOp.IsKnownSimple_2 = false;
            topoOp.Simplify();
            return polygon;
        }

        private static IPolygon CreateDonutPolygon()
        {
            var polygon = (IPolygon)new PolygonClass();
            var pointcoll = (IPointCollection4)polygon;
            var geometryBridge2 = (IGeometryBridge2)new GeometryEnvironmentClass();
            var aWKSPointBuffer = new WKSPoint[5];

            // Exterior ring
            aWKSPointBuffer[0].X = 0;
            aWKSPointBuffer[0].Y = 0;
            aWKSPointBuffer[1].X = 0;
            aWKSPointBuffer[1].Y = 4;
            aWKSPointBuffer[2].X = 4;
            aWKSPointBuffer[2].Y = 4;
            aWKSPointBuffer[3].X = 4;
            aWKSPointBuffer[3].Y = 0;
            aWKSPointBuffer[4].X = 0;
            aWKSPointBuffer[4].Y = 0;
            geometryBridge2.AddWKSPoints(pointcoll, ref aWKSPointBuffer);

            // Interior ring
            aWKSPointBuffer[0].X = 1;
            aWKSPointBuffer[0].Y = 1;
            aWKSPointBuffer[1].X = 3;
            aWKSPointBuffer[1].Y = 1;
            aWKSPointBuffer[2].X = 3;
            aWKSPointBuffer[2].Y = 3;
            aWKSPointBuffer[3].X = 1;
            aWKSPointBuffer[3].Y = 3;
            aWKSPointBuffer[4].X = 1;
            aWKSPointBuffer[4].Y = 1;
            geometryBridge2.AddWKSPoints(pointcoll, ref aWKSPointBuffer);

            var topoOp = (ITopologicalOperator2)polygon;
            topoOp.IsKnownSimple_2 = false;
            topoOp.Simplify();
            return polygon;
        }

        private static IPolygon CreateMultiPartPolygon()
        {
            var polygon = (IPolygon)new PolygonClass();
            var pointcoll = (IPointCollection4)polygon;
            var geometryBridge2 = (IGeometryBridge2)new GeometryEnvironmentClass();
            var aWKSPointBuffer = new WKSPoint[5];

            // Exterior ring 1
            aWKSPointBuffer[0].X = 0;
            aWKSPointBuffer[0].Y = 0;
            aWKSPointBuffer[1].X = 0;
            aWKSPointBuffer[1].Y = 1;
            aWKSPointBuffer[2].X = 1;
            aWKSPointBuffer[2].Y = 1;
            aWKSPointBuffer[3].X = 1;
            aWKSPointBuffer[3].Y = 0;
            aWKSPointBuffer[4].X = 0;
            aWKSPointBuffer[4].Y = 0;
            geometryBridge2.AddWKSPoints(pointcoll, ref aWKSPointBuffer);

            // Exterior ring 2
            aWKSPointBuffer[0].X = 2;
            aWKSPointBuffer[0].Y = 2;
            aWKSPointBuffer[1].X = 2;
            aWKSPointBuffer[1].Y = 3;
            aWKSPointBuffer[2].X = 3;
            aWKSPointBuffer[2].Y = 3;
            aWKSPointBuffer[3].X = 3;
            aWKSPointBuffer[3].Y = 2;
            aWKSPointBuffer[4].X = 2;
            aWKSPointBuffer[4].Y = 2;
            geometryBridge2.AddWKSPoints(pointcoll, ref aWKSPointBuffer);

            var topoOp = (ITopologicalOperator2)polygon;
            topoOp.IsKnownSimple_2 = false;
            topoOp.Simplify();
            return polygon;
        }
    }
}

Here is the output of the program at 10.1 SP1 when run without debugging (after closing the crash dialog) and targeting .NET 3.5:

1
2
2
1
1

Unhandled Exception: System.BadImageFormatException: Array type not expected here.
   at ESRI.ArcGIS.Geometry.PolygonClass.QueryExteriorRingsEx(Int32 numExteriorRingsRequested, IRing& exteriorRings)
   at RingTest1.Program.d__0.MoveNext() in C:\CSProjects\RingTest1\RingTest1\Program.cs:line 32
   at System.Linq.Enumerable.Count[TSource](IEnumerable`1 source)
   at RingTest1.Program.Main(String[] args) in C:\CSProjects\RingTest1\RingTest1\Program.cs:line 25

Here is the output on .NET 4.0:

1
2
2
1
1
2
share|improve this answer
1  
+1 Great sleuthing! This makes a good case for moving to 4.0. –  Kirk Kuykendall Feb 11 '13 at 16:45
    
Thanks, good work! –  Jakub Feb 11 '13 at 18:44
add comment

Note that the IPolygon2.QueryExteriorRingsEx contract is not entirely compatible with COM-to-.NET conversion: It is supposed to return an array reference but in .NET returns a reference to the first array element.

Compare this with, say, ISegmentCollection.QuerySegments vs IGeometryBridge.QuerySegments, where the signature of the latter is adapted for .NET.

The documentation for IPolygon2.QueryExteriorRingsEx explicitly states:

Does not work in .NET. Use IPolygon4.ExteriorRingBag instead.

Calling IPolygon2.QueryExteriorRingsEx may work in .NET 4.0, but do not rely on this. This behavior depends on how the data is marshalled and on how CLR moves it around in memory. It is not guaranteed to be consistent between runs, Esri releases, or .NET framework iterations.

share|improve this answer
    
Thanks Petr. I think the documentation says it only doesn't work in C#. –  Jakub Feb 11 '13 at 18:47
    
Thanks for the explanation, I was wondering why the signature did not include array syntax. –  blah238 Feb 11 '13 at 18:48
    
@Jakub, I think my testing along with your reports suggest the situation is the same for both VB.NET and C#. After all they are both .NET and the CLR has the final say on what works :) –  blah238 Feb 11 '13 at 18:50
    
Yes, true. What I meant is that it incorrectly states that this only occurs in C#. So either this is a typo in the documentation or a bug. Thank you both for your help. Most thorough! –  Jakub Feb 11 '13 at 18:53
1  
The underlying matter is that the original signature (which would be HRESULT QueryExteriorRingsEx(long numExteriorRingsRequested, IRing** exteriorRings); in C++) is ambiguous from the .NET type library importer perspective: exteriorRings can mean either a ring pointer or a ring array pointer. The issue will manifest itself in every .NET programming language. –  Petr Krebs Feb 11 '13 at 19:17
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.