# How to identify feature vertices that are part of a donut hole in ArcGIS 10?

I´m exporting the coordinates of polygon vertices to a CSV-File using the "Feature Vertices to Points" and then the "Export feature attributes to ASCII" tools.

Some of these polygons have holes (donuts). Can I do the analysis in a way, that in the resulting CSV-File it´s possible to identify if a point has been part of the outer border or the inner border (donut) of the polygon?

Yet another option, this is more of a theory and programmatic one, using arcpy.

A polygon can consist not only of a single outer ring with a single inner donut hole -- they can be nested to an arbitrary number of levels.

Consider the following:

Difference between outer and inner rings http://edndoc.esri.com/arcobjects/8.3/componenthelp/esricore/.%5Cbitmaps%5CGeomIsExterior.gif

A topologically correct polygon's rings are ordered according to their containment relationship (source). Based on my results below this appears to be in order of innermost to outermost with exterior rings being listed before the interior rings within them.

Additionally interior rings (green lines) are always within exterior rings (red lines). It is possible to have rings that overlap each other, self-intersections, etc., but typically these are considered topologically incorrect and are simplified before they are stored.

Another important point is the distinction between parts and rings. A feature can have multiple parts, and a part can have multiple rings. In the picture below, think of each solid red shape as an individual part, each having a single exterior ring and 0, 1, or more inner rings.

(source: arcgis.com)

For each part, the first ring is the outer ring, while all subsequent rings are inner rings. The vertices of outer rings are oriented in a clockwise fashion while inner rings are oriented counter-clockwise.

Now to get practical:

You can use the geometry objects in arcpy to access the parts, rings, and vertices of a feature. There is a null point between the rings of a part. You could iterate over the parts and points, checking for the null point to see if there are interior rings.

See the Python script below. This defines a generator function to list the X, Y, FID, part, ring, and vertex indexes which is called repeatedly within a SearchCursor to write to a CSV file using the `csv` module.

The FID, part, and ring indices uniquely identify each ring, and you know that if the ring index is 0 it's an exterior ring. If the ring index is greater than 0, it's an interior ring. One tweak you might want to make is to remove the last point of each ring as it will always be the same as the first point, to make a closed ring. To do that just set `skiplastvertex = True` near the top of the script. I used True in the CSV output listed below.

``````import arcpy, csv

fc = r"C:\GISData\test.gdb\ringtest2"
csvfile = r"C:\GISData\ringtest2.csv"
header = ['X', 'Y', 'FID', 'PART', 'RING', 'VERTEX']
skiplastvertex = False

def iterateRingsAndVertices(shape, fid, skiplastvertex=False):
for partindex, part in enumerate(shape):
ringindex = 0
vertexindex = 0
pnt = part.next()
while pnt:
output = [pnt.X, pnt.Y, fid, partindex, ringindex, vertexindex]
pnt = part.next()
if pnt is None: # Check if this is last point in ring
if not skiplastvertex:
yield output # Return the last point in ring
pnt = part.next() # Check for inner ring
if pnt:
vertexindex = 0
ringindex += 1
else:
yield output
vertexindex += 1

if __name__ == "__main__":
# Open text file for writing
with open(csvfile, 'wb') as f:
w = csv.writer(f)
desc = arcpy.Describe(fc)
shapeField = desc.shapeFieldName
oidField = desc.OIDFieldName
rows = arcpy.SearchCursor(fc)
for row in rows:
oid = row.getValue(oidField)
shape = row.getValue(shapeField)
w.writerows(iterateRingsAndVertices(shape, oid, skiplastvertex))
``````

Example output with screenshot of test dataset:

Screenshot of test dataset http://img406.imageshack.us/img406/6293/3df0e6d59ae3480d82effac.png

```X       Y       FID      PART     RING     VERTEX
-------------------------------------------------
6.25    3.75    1        0        0        0
3.75    3.75    1        0        0        1
3.75    6.25    1        0        0        2
6.25    6.25    1        0        0        3
10.00   10.00   1        1        0        0
10.00   0.00    1        1        0        1
0.00    0.00    1        1        0        2
0.00    10.00   1        1        0        3
2.50    7.50    1        1        1        0
2.50    2.50    1        1        1        1
7.50    2.50    1        1        1        2
7.50    7.50    1        1        1        3
```

I was able to import the CSV file into ArcMap, display it as XY data, and label it without much fuss. You could of course also join it back to your original feature class and work with it that way, export it to another feature class or table, etc. Hopefully this helps!

• Wow, that´s great and perfectly what I need. I´m still a beginner with arcpy but I think I can do it like that. Jun 13, 2012 at 6:24

Here is another option:

1. Run Feature Vertices to Points tool on polygon layer.
2. Run Feature Class to Coverage tool on polygon layer.
3. Run Dissolve tool on the result of step 2
4. Run Feature Vertices to Points tool on the result of step 3 (gives only outer vertices).
5. Perform either a Select By Location or Feature Compare tool between step 1 and step 4 point layer to distinguish between inner and outer vertices.

I've come up with a solution but there may be a better way.

You'd need to add a few extra steps.

1. Polygon to Line; This will create two lines for donuts. One the inner boundary, one for the outer.

2. Measure the lengths of Line. The inner loop will always be shorter. The lines created will have an attribute that associates them both to the same original polygon allowing you to compare. Give the shorter line an attribute to reflect its the inner loop of a donut.

3. Feature Vertices to Points (from the Line layer you created). The points will retain the attribute you created earlier.

4. Export feature Attributes to ASCII tools.

That works for me. If you have to do it for lots of polygons you may wish to automate it using the model builder. In that case Step 2 would be the most difficult but could probably be done with the field calculator and a few "select by attributes".

• Thanks a lot. That sounds promising. I will give it a try. Jun 12, 2012 at 12:00