As adding null point as it was suggested here gave a point with (0,0) cordinates, I searched existing scripts in ESRI repository and I've found another way of separating polygon rings as well as polyline parts.
The solution is based on creating two arrays: featureVertexArray
for storing whole geometry and partVertexArray
for storing separate rings:
try:
rows = gp.SearchCursor(inFC) #open SearchCursor on input FC
row = rows.next()
outRows = gp.InsertCursor(outFC) #open InsertCursor on output FC
#create Array object which will contain features vertices
featureVertexArray = gp.CreateObject("Array")
partVertexArray = gp.CreateObject("Array")
while row:
feature = row.getValue(shapeField) #get the SHAPE field into variable
vertex = gp.CreateObject("Point") #empty Point object to store geometry
##For point geometry there is another way of reading coordinates than for polyline/polygon
if shapeType.upper() == "POINT" or shapeType.upper() == "MULTIPOINT":
#this part works right
else:
#feature can have multiple parts - first goes iteration through parts
partNum = 0
partCount = feature.PartCount
while partNum < partCount:
part = feature.GetPart(partNum) #the output is Array of points
pnt = part.next() #take first point from Array of points
#iterate through all points in array
while pnt:
#for each geometry create new POINT obj and assign swapped Y, X. Then add vertices to ARRAY
vertex.X, vertex.Y = pnt.Y, pnt.X
partVertexArray.add(vertex)
pnt = part.next()
#If pnt is null, either the part is finished or there is an interior ring
if not pnt:
pnt = part.next()
featureVertexArray.add(partVertexArray)
partVertexArray.removeAll()
#if pnt:
#print "Interior:"
featureVertexArray.add(partVertexArray)
partNum += 1 #increment part number to run loop
newFeature = outRows.newRow() #create new row in InsCur
newFeature.shape = featureVertexArray #assign ARRAY filled with points to shape field
outRows.insertRow(newFeature) #insert new row
featureVertexArray.removeAll() #clear ARRAYS before processing new geometry
partVertexArray.removeAll()
row = rows.next()
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)
w.writerow(header) # Write header row
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!
Best Answer
Since celticflute did not post his code, I had to figure it out again.
Finally got a working sample. This seems to work pretty well.
The key thing is that you do not write polygon parts and holes differently!
Instead, you construct a polygon from nested arrays which represent parts made of up rings. When you write the geometry by writing it to a shape field or use it in a tool, behind the scenes arc objects planarizes the rings within in each polygon part and determines what's a hole and what isn't. And sorts the coordinates clockwise, and burns the xys into the coordinate system and domain of the geodatabase feature class.