Manipulating polygon features using ArcPy

Dr. Huidae Cho
Institute for Environmental and Spatial Analysis...University of North Georgia

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.