I am using ArcMap 10.0.

I have a trick for removing the donut holes, which is converting the polygon to line and then creating polygon out of the lines and in the end merging all of them. The drawback of this strategy is that in removes other holes and empty areas which in reality are empty and should be kept empty. See below image:

enter image description here


7 Answers 7


It's only the threshold size above which holes should not be filled that prevents this being a duplicate of Filling in all Donut Polygons using ArcMap.

I would try the Union tool with NO_GAPS:

NO_GAPS —A feature will be created for the areas in the output that are completely enclosed by polygons. This feature will have blank attributes.

You can then select the features with blank attributes below a threshold size and calculate their attributes to be the same as the original polygon - or copy/paste them in the Editor's Attribute window.

Features with blank attributes above the threshold size should be deleted.

From there the Dissolve tool should complete the job.


You can use "Eliminate Polygon Part" tool (Data Management Tools -> Generalization) in ArcToolbox. You can specify minimum hole area/percentage to remove.

  • 1
    This function requires Advanced/ArcInfo license level though.
    – Martin
    Apr 22, 2014 at 11:51
  • 1
    I tried the Eliminate Polygon Part tool, in ArcGIS Pro 2.0. But it did nothing to the polygon feature I was testing. The feature had several interior rings, and I want them all to be gone. Does this tool only work if the feature consists of multipart geometry?
    – Mike Finch
    Aug 23, 2017 at 23:11
  • Not "only". It works single polygons with hole(s), too. Mar 17, 2018 at 23:01

@PolyGeo's solution is one I have employed myself and works well. Another alternative is to buffer your polygons by a small amount and then de-buffer the result by the same amount (say +1m and then -1m). This also works well but carries a small risk of accidentally joining polygons that neighbour by the same amount as your buffer. It can also increase your vertex count (which may or may not be a problem).

You could also write a script for a robust solution by iterating over each polygon, creating temporary polygons based on the interior rings and testing these for their areas. If they are below your threshold, you then remove that interior ring from the geometry and update your row. This solution is more work than @PolyGeo's solution or my buffer hack above but is possibly a "purer" approach.


Here's another way to remove lakes from polygons (like the zip code map layer from Navteq). This is also useful to clean up territory map layers that have been generated by dissolving the zip code map layer by the territory code.

  1. Use a Geoprocessing "Union" on the polygon map layer and remove the checkbox for “Gaps Allowed” (which is checked by default.) This creates polygons for all of the "donut" holes in the polygons.
  2. In the resulting Union map layer, add fields for ID, X, and Y. The ID should be separate from the name/ID of the polygons that have the donut holes that you want to remove.
  3. In the attribute table, use the Field Calculator to calculate the ID from the ObjectID field and use "Calculate Geometry" to fill in the X and Y values.
  4. Separate the donut polygons from the other polygons by selecting (Select by Attributes) those where the name field is null.
  5. Export the selected records to a new table. (table options dropdown-->Export...)
  6. Use the "Add XY" option to create an event layer. (right click layer-->Display XY Data....
  7. Export the event layer to a new feature class. (right click the layer-->Data-->Export data)
  8. Do a spatial join between the new point layer and the ORIGINAL polygon map layer that does not have the donut polygons. Use the “closest” option so that the name of the surrounding polygon gets appended to the attribute table of the donut polygons.
  9. Use a standard table join between the joined point layer and the resulting map layer from the Union (based on the ID field). (right click layer-->Joins and Relates-->Join-->Join data from another layer based on spatial location)
  10. Use the Field Calculator in the attribute table to calculate the null polygon names from the joined point layer.
  11. Run a Geoprocessing "Dissolve" based on the polygon name to merge the donut polygons with the larger named polygon.

Note that this will not correct the problem of the rivers that are cut into the outline of the polygon, but it does improve the original map layer by removing the inner donut holes.


If polygons in the feature class contain other polygons in the same feature class (ex. Lesotho inside of South Africa) and if you need to eliminate manual editing in favor of ModelBuilder, etc., using "Eliminate Polygon Part" is the quickest solution. Tool settings:

-"Condition (optional)" = AREA -"Area (optional)" = a very large number (say, 1e+100) -"Eliminate contained parts only (optional)" = [checked]

This will yield a South Africa which has Lesotho's area filled in, essential for workflows such as outputting vertices for use in Polygon visuals in Tableau.


Since it hasn't been mentioned so far and it seems to be the right tool for exactly what you are trying to achieve: Aggregate Polygons (see for ArcGIS Pro and for ArcGIS Desktop)

Set the minimum_hole_size to the minimum size of a polygon hole you want to keep. Smaller ones will be eliminated. In the example below holes smaller than 30 square meters are removed.

enter image description here enter image description here

Also the optional minimum_area value in the same tool does the same for the polygon parts that are not holes.

def removePolygonRings(shape, minimum_area, geographic_srid, projected_srid):
    arcpy_point = arcpy.Point()

    polygons_list = []
    polygons = shape.getPart()

    polygons_length = len(polygons)
    print("TOTAL POLYGONS: {}\n".format(polygons_length))

    for polygon in polygons:
        points = [point for point in polygon]

        if not None in points:
            final_polygon = arcpy.Polygon(polygon, geographic_srid)

            print("{} - HAS NO RINGS\n".format(polygons_length))
            polygons_length -= 1


        print("{} - HAS RINGS".format(polygons_length))

        polygon_polygons = len(list(filter(lambda point: point == None, points)))
        rings_length = polygon_polygons - 1
        print("TOTAL RINGS: {}".format(rings_length))

        points_list = []
        final_polygons_array = arcpy.Array()
        for point in points:
            if point == None:
                current_polygon_array = arcpy.Array()
                for point_item in points_list:
                        arcpy.PointGeometry(point_item, geographic_srid)

                current_polygon = arcpy.Polygon(current_polygon_array, projected_srid)
                current_polygon_area = current_polygon.area

                boundary_or_ring = (
                    "BOUNDARY" if polygon_polygons == rings_length + 1 else "RING"
                print("{} - AREA: {}".format(boundary_or_ring, current_polygon_area))

                if current_polygon_area > minimum_area:
                    print("RING > {}".format(minimum_area))
                    final_polygons_list = []
                    for point_item in points_list:
                            arcpy.PointGeometry(point_item, geographic_srid).centroid


                if rings_length == 0:
                    if final_polygons_array[-1] == None:
                        final_polygons_array.remove(len(final_polygons_array) - 1)

                    final_polygon = arcpy.Polygon(final_polygons_array, geographic_srid)


                points_list = []
                rings_length -= 1

            arcpy_point.X = point.X
            arcpy_point.Y = point.Y


        polygons_length -= 1

    temp_merge = "in_memory/temp_merge"
    arcpy.Merge_management(polygons_list, temp_merge)

    temp_dissolve = "in_memory/temp_dissolve"
    arcpy.Dissolve_management(temp_merge, temp_dissolve)

    final_polygon_shape = arcpy.da.SearchCursor(temp_dissolve, ["SHAPE@"]).next()[0]

    del temp_merge
    del temp_dissolve

    return final_polygon_shape

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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