# Deleting collinear vertices from polygon feature class using ArcPy

I have a shapefile with polygons and want to write the Python script to eliminate collinear vertices in a polygon. I found a solution for the line feature class, but not for polygon: Deleting collinear vertex / vertices using ArcMap.

``````import arcpy

#----------------------------------------------------------------------
def are_collinear(p1, p2, p3, tolerance=0.5):
"""return True if 3 points are collinear.
tolerance value will decide whether lines are collinear; may need
to adjust it based on the XY tolerance value used for feature class"""
x1, y1 = p1, p1
x2, y2 = p2, p2
x3, y3 = p3, p3
res = x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2)
if -tolerance <= res <= tolerance:
return True

#----------------------------------------------------------------------
def get_redundant_vertices(vertices):
"""get redundant vertices from a line shape vertices"""
indexes_of_vertices_to_remove = []
start_idx, middle_index, end_index = 0, 1, 2
for i in range(len(vertices)):
p1, p2, p3 = vertices[start_idx:end_index + 1]
if are_collinear(p1, p2, p3):
indexes_of_vertices_to_remove.append(middle_index)

start_idx += 1
middle_index += 1
end_index += 1
if end_index == len(vertices):
break
return indexes_of_vertices_to_remove

#----------------------------------------------------------------------
def clean_geometries(fc, densify_curves=False):
"""clean polyline features in the fc removing redundant vertices"""
in_sr = arcpy.Describe(fc).spatialReference.factoryCode

with arcpy.da.UpdateCursor(fc, ['SHAPE@', 'OID@']) as ucur:
for row in ucur:
print "OBJECTID", row
cleaned_parts = []
shape = row

if 'curvePaths' in shape.JSON:
if densify_curves:
shape = shape.densify('DISTANCE', 1, 1)
else:
continue

for part in range(shape.partCount):
vertices = [(p.X, p.Y) for p in shape.getPart(part)]
if len(vertices) < 3:  #polyline's part consists of 2 vertices
continue
vertices_to_remove = get_redundant_vertices(vertices)
vertices_to_keep = [
val for idx, val in enumerate(vertices)
if idx not in vertices_to_remove
]
cleaned_part_as_array = arcpy.Array(
[arcpy.Point(*coords) for coords in vertices_to_keep])
cleaned_parts.append(cleaned_part_as_array)

if cleaned_parts:
cleaned_shape = arcpy.Polyline(
arcpy.Array(cleaned_parts), in_sr)
row = cleaned_shape
ucur.updateRow(row)

if __name__ == '__main__':
clean_geometries(fc, densify_curves=False)
``````

If I use this code for a polygon FC it will delete my polygons. I am new to Python and I don't know how to fix it in ArcPy.

Could you give me some tips or ideas how to achieve it? • Any Ramer-Douglas-Peucker implementation with a really tight threshold ought to accomplish this. For ArcGIS, with polygons, that could be `arcpy.SimplifyPolygon_cartography`. Feb 14, 2021 at 14:01
• @Vince actually I tried RDP algorithm. If I use threshold like 0.5 it deletes some of the needed vertices, if I set 0.001 it deletes nothing. E.g.: s = df.simplify(tolerance=0.001, preserve_topology=True) Feb 14, 2021 at 14:10

I believe you only need to edit a few lines of code.

The lines I have edited/added are marked with ### at the end of the line. The key method is `boundary()`, it returns the polygon as a polyline and feeds that into the rest of the existing logic. The update cursor also now updates the geometry as a polygon.

So this code now expects the input to be a polygon dataset, not tested it with multi-parts either.

``````import arcpy

#----------------------------------------------------------------------
def are_collinear(p1, p2, p3, tolerance=0.5):
"""return True if 3 points are collinear.
tolerance value will decide whether lines are collinear; may need
to adjust it based on the XY tolerance value used for feature class"""
x1, y1 = p1, p1
x2, y2 = p2, p2
x3, y3 = p3, p3
res = x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2)
if -tolerance <= res <= tolerance:
return True

#----------------------------------------------------------------------
def get_redundant_vertices(vertices):
"""get redundant vertices from a line shape vertices"""
indexes_of_vertices_to_remove = []
start_idx, middle_index, end_index = 0, 1, 2
for i in range(len(vertices)):
p1, p2, p3 = vertices[start_idx:end_index + 1]
if are_collinear(p1, p2, p3):
indexes_of_vertices_to_remove.append(middle_index)

start_idx += 1
middle_index += 1
end_index += 1
if end_index == len(vertices):
break
return indexes_of_vertices_to_remove

#----------------------------------------------------------------------
def clean_geometries(fc, densify_curves=False):
"""clean polyline features in the fc removing redundant vertices"""
in_sr = arcpy.Describe(fc).spatialReference.factoryCode

with arcpy.da.UpdateCursor(fc, ['SHAPE@', 'OID@']) as ucur:
for row in ucur:
print "OBJECTID", row
cleaned_parts = []
poly = row ###
shape = poly.boundary() ###

if 'curvePaths' in shape.JSON:
if densify_curves:
shape = shape.densify('DISTANCE', 1, 1)
else:
continue

for part in range(shape.partCount):
vertices = [(p.X, p.Y) for p in shape.getPart(part)]
if len(vertices) < 3:  #polyline's part consists of 2 vertices
continue
vertices_to_remove = get_redundant_vertices(vertices)
vertices_to_keep = [
val for idx, val in enumerate(vertices)
if idx not in vertices_to_remove
]
cleaned_part_as_array = arcpy.Array(
[arcpy.Point(*coords) for coords in vertices_to_keep])
cleaned_parts.append(cleaned_part_as_array)

if cleaned_parts:
cleaned_shape = arcpy.Polygon(arcpy.Array(cleaned_parts), in_sr) ###
row = cleaned_shape
ucur.updateRow(row)

if __name__ == '__main__':
fc = r'C:\Scratch\fGDB_Scratch.gdb\testpoly_1'
• I think the issue is with the coordinate system, yours being in WGS84 and the tolerance setting for the function `are_collinear`. 0.5 would be 0.5 decimal degrees, not sure what that would be in metres. So you need to re-project your data to be using such code, Looking on SpatialReference website there is this coordinate system for Kazan? Feb 16, 2021 at 18:47