Manipulating polygon features using ArcPy
1 Single- and multi-part polygons
Single-part polygons
arcpy.Polygon(
arcpy.Array([
arcpy.Point(x, y),
...]))
Multi-part polygons
arcpy.Polygon(
arcpy.Array([
arcpy.Array([
arcpy.Point(x, y),
...]),
arcpy.Array([
arcpy.Point(x, y),
...]),
...]))
2 Outer and inner rings
Single-part polygons with inner rings (holes)
arcpy.Polygon(
arcpy.Array([
arcpy.Array([
arcpy.Point(x, y), # the biggest (not necessarily the first) polygon will
...]), # be the outer ring and the other polygons should completely
arcpy.Array([ # be contained within the outer ring to be inner rings;
arcpy.Point(x, y), # otherwise, holes and new parts will be created
...]),
...]))
This syntax is exactly the same as that for multi-part polygons. ArcPy internally handles outer- and inner-ring creation. It can even handle parts crossing the boundaries of other parts, and create inner rings and additional parts automatically.
We don’t have to worry about the order of points in outer and inner rings when creating polygons. However, when these rings are retrieved, the order of points in outer rings is in a clockwise direction while that in inner rings, in a counter-clockwise direction in the same part as the outer ring separated by a null point.
3 Create polygons
Use the InsertCursor
and Polygon
to add new polygons.
# create a new shapefile
# arcpy.CreateFeatureclass_management returns a Result object; take the first item, which is the full path
fc = arcpy.CreateFeatureclass_management(r'P:\tmp', 'test_polygons.shp', 'POLYGON')[0]
# add some fields
arcpy.AddField_management(fc, 'length', 'DOUBLE')
arcpy.AddField_management(fc, 'area', 'DOUBLE')
# get the extent of the active map
ext = arcpy.mp.ArcGISProject('CURRENT').activeMap.defaultView.camera.getExtent()
# here we'll insert geometry, length, and area
pnts = []
with arcpy.da.InsertCursor(fc, ['SHAPE@', 'length', 'area']) as cur:
pnts.clear()
# clockwise/counter-clockwise doesn't matter!
pnts.append(ext.lowerLeft)
pnts.append(ext.upperLeft)
pnts.append(ext.upperRight)
pnts.append(ext.lowerRight)
pnts.append(ext.lowerLeft) # close or not? both should work!
polygon = arcpy.Polygon(arcpy.Array(pnts))
cur.insertRow([polygon, polygon.length, polygon.area])
4 Update polygons
center = arcpy.Point((ext.lowerLeft.X + ext.upperRight.X) / 2, (ext.lowerLeft.Y + ext.upperRight.Y) / 2)
# retrieve all features (just one feature)
with arcpy.da.UpdateCursor(fc, ['SHAPE@', 'length', 'area']) as cur:
for row in cur:
polygon = row[0]
pnts = [center]
pnts.extend(polygon[0][0:4])
pnts.append(center)
# update the current feature
polygon = arcpy.Polygon(arcpy.Array(pnts))
cur.updateRow([polygon, polygon.length, polygon.area])
5 Create polygons with holes
hole_w = ext.lowerLeft.X + (ext.upperRight.X - ext.lowerLeft.X) / 4
hole_e = ext.lowerLeft.X + (ext.upperRight.X - ext.lowerLeft.X) * 3 / 4
hole_s = ext.lowerLeft.Y + (ext.upperRight.Y - ext.lowerLeft.Y) * 5 / 8
hole_n = ext.lowerLeft.Y + (ext.upperRight.Y - ext.lowerLeft.Y) * 7 / 8
# retrieve all features (just one feature)
with arcpy.da.UpdateCursor(fc, ['SHAPE@', 'length', 'area']) as cur:
for row in cur:
polygon = row[0]
# copy the first part
pnts = []
pnts.extend(polygon[0][0:len(polygon[0])])
# inner ring counter-clockwise? it doesn't matter again
hole = [arcpy.Point(hole_w, hole_s),
arcpy.Point(hole_e, hole_s),
arcpy.Point(hole_e, hole_n),
arcpy.Point(hole_w, hole_n)]
# create a single-part polygon with a hole
polygon = arcpy.Polygon(arcpy.Array([arcpy.Array(pnts), arcpy.Array(hole)]))
cur.updateRow([polygon, polygon.length, polygon.area])
6 Create multi-part polygons
part2_w = hole_e + ext.width * 0.1
part2_e = part2_w + ext.width * 0.5
part2_s = hole_s
part2_n = hole_n
# retrieve all features (just one feature)
with arcpy.da.UpdateCursor(fc, ['SHAPE@', 'length', 'area']) as cur:
for row in cur:
polygon = row[0]
# copy the first part
pnts = []
pnts.extend(polygon[0][0:len(polygon[0])])
# inner ring counter-clockwise? it doesn't matter again
hole = [arcpy.Point(hole_w, hole_s),
arcpy.Point(hole_e, hole_s),
arcpy.Point(hole_e, hole_n),
arcpy.Point(hole_w, hole_n)]
# part 2 outer ring counter-clockwise? that's ok
part2 = [arcpy.Point(part2_w, part2_s),
arcpy.Point(part2_e, part2_s),
arcpy.Point(part2_e, part2_n),
arcpy.Point(part2_w, part2_n)]
# create a multi-part polygon with a hole and part 2
polygon = arcpy.Polygon(arcpy.Array([arcpy.Array(pnts), arcpy.Array(hole), arcpy.Array(part2)]))
cur.updateRow([polygon, polygon.length, polygon.area])
7 Read polygons
# retrieve all features (just one feature)
with arcpy.da.SearchCursor(fc, ['SHAPE@']) as cur:
for row in cur:
polygon = row[0]
# we know this polygon has a hole; how many parts?
print(f'Parts: {polygon.partCount}')
for i in range(polygon.partCount):
# or for part in polygon: if you don't need the index number
print(f'Part {i}')
part = polygon.getPart(i)
for point in part:
if point:
print(f'{point.X}, {point.Y}')
else:
print('Inner ring')
8 Exercise: Draw random circles
9 Homework: Draw random rings
Develop a brand new toolbox that draws random rings within the project map extent.
Parameters
- Number of rings
- Minimum outer radius in meters
- Maximum outer radius in meters
- Inner radius factor in %
- Output feature class
This tool should create random rings (one outer circle and one inner circle for each ring). The inner circle should be a hole in the outer circle and its radius, the factor times the outer radius. Use the nc_spm_08_grass7_exercise sample data set for spatial reference.