Fix invalid polygon in Shapely
Question:
Shapely defines a Polygon as invalid if any of its segments intersect, including segments that are colinear. Many software packages will create a region or area with a “cutout” as shown here which has colinear segments:
>>> pp = Polygon([(0,0), (0,3), (3,3), (3,0), (2,0),
(2,2), (1,2), (1,1), (2,1), (2,0), (0,0)])
>>> pp.is_valid
WARNING:shapely.geos:Self-intersection at or near point 2 0
False
Naturally, the cutout can be implemented natively in Shapely, or this same geometry can be implemented as two valid polygons, but if I only have the list of points shown above, is there an easy to “fix” this (create valid geometry from this list of points)?
Answers:
I found a solution that works for the specific case given:
>>> pp2 = pp.buffer(0)
>>> pp2.is_valid
True
>>> pp2.exterior.coords[:]
[(0.0, 0.0), (0.0, 3.0), (3.0, 3.0), (3.0, 0.0), (2.0, 0.0), (0.0, 0.0)]
>>> pp2.interiors[0].coords[:]
[(2.0, 1.0), (2.0, 2.0), (1.0, 2.0), (1.0, 1.0), (2.0, 1.0)]
Untested, but it appears that Shapely have added a function to support this now.
https://shapely.readthedocs.io/en/latest/manual.html#validation.make_valid
Shapely implemented a solution for this. Through pip you can use any shapely version >= 1.8a3 and import this way:
from shapely.validation import make_valid
The current version of shapely available via pip and conda includes the make_valid
function. If you need to install an older version of shapely, you can use the shapely implementation as shown below:
def make_valid(ob):
from shapely.geometry.base import geom_factory
from shapely.geos import lgeos
if ob.is_valid:
return ob
return geom_factory(lgeos.GEOSMakeValid(ob._geom))
I have used the .buffer(0) method a lot, but it gave different results on Windows and on Linux. Therefore if you encounter such a problem, remember that:
Warning: The .buffer(0) function of shapely may behave different on different operating systems, i.e. Windows and Linux. I had examples, where on Linux an empty Polygon is returned while on Windows the correct non-empty Polygon is returned!
This problem took me days to solve, that’s why I want to add that as an answer (my suggested edit to the accepted answer was sadly rejected).
simplest solution
new_polygon = pp.buffer(0)
new_polygon.is_valid
maybe it would be work.
This is my first attempt at a fix geometry function. I had to handle the special case where a polygons are converted to multipolygons, where make_valid
doesn’t work b/c len(make_valid(feature)) !=1
.
from shapely.validation import make_valid
def fix_geom(in_feature):
# avoid changing original geodf
in_feature = in_feature.copy(deep=True)
# drop any missing geometries
in_feature = in_feature[~(in_feature.is_empty)]
# Repair broken geometries
for index, row in in_feature.iterrows(): # Looping over all polygons
if row['geometry'].is_valid:
next
else:
fix = make_valid(row['geometry'])
try:
in_feature.loc[[index],'geometry'] = fix # issue with Poly > Multipolygon
except ValueError:
in_feature.loc[[index],'geometry'] = in_feature.loc[[index], 'geometry'].buffer(0)
return in_feature
Shapely defines a Polygon as invalid if any of its segments intersect, including segments that are colinear. Many software packages will create a region or area with a “cutout” as shown here which has colinear segments:
>>> pp = Polygon([(0,0), (0,3), (3,3), (3,0), (2,0),
(2,2), (1,2), (1,1), (2,1), (2,0), (0,0)])
>>> pp.is_valid
WARNING:shapely.geos:Self-intersection at or near point 2 0
False
Naturally, the cutout can be implemented natively in Shapely, or this same geometry can be implemented as two valid polygons, but if I only have the list of points shown above, is there an easy to “fix” this (create valid geometry from this list of points)?
I found a solution that works for the specific case given:
>>> pp2 = pp.buffer(0)
>>> pp2.is_valid
True
>>> pp2.exterior.coords[:]
[(0.0, 0.0), (0.0, 3.0), (3.0, 3.0), (3.0, 0.0), (2.0, 0.0), (0.0, 0.0)]
>>> pp2.interiors[0].coords[:]
[(2.0, 1.0), (2.0, 2.0), (1.0, 2.0), (1.0, 1.0), (2.0, 1.0)]
Untested, but it appears that Shapely have added a function to support this now.
https://shapely.readthedocs.io/en/latest/manual.html#validation.make_valid
Shapely implemented a solution for this. Through pip you can use any shapely version >= 1.8a3 and import this way:
from shapely.validation import make_valid
The current version of shapely available via pip and conda includes the make_valid
function. If you need to install an older version of shapely, you can use the shapely implementation as shown below:
def make_valid(ob):
from shapely.geometry.base import geom_factory
from shapely.geos import lgeos
if ob.is_valid:
return ob
return geom_factory(lgeos.GEOSMakeValid(ob._geom))
I have used the .buffer(0) method a lot, but it gave different results on Windows and on Linux. Therefore if you encounter such a problem, remember that:
Warning: The .buffer(0) function of shapely may behave different on different operating systems, i.e. Windows and Linux. I had examples, where on Linux an empty Polygon is returned while on Windows the correct non-empty Polygon is returned!
This problem took me days to solve, that’s why I want to add that as an answer (my suggested edit to the accepted answer was sadly rejected).
simplest solution
new_polygon = pp.buffer(0)
new_polygon.is_valid
maybe it would be work.
This is my first attempt at a fix geometry function. I had to handle the special case where a polygons are converted to multipolygons, where make_valid
doesn’t work b/c len(make_valid(feature)) !=1
.
from shapely.validation import make_valid
def fix_geom(in_feature):
# avoid changing original geodf
in_feature = in_feature.copy(deep=True)
# drop any missing geometries
in_feature = in_feature[~(in_feature.is_empty)]
# Repair broken geometries
for index, row in in_feature.iterrows(): # Looping over all polygons
if row['geometry'].is_valid:
next
else:
fix = make_valid(row['geometry'])
try:
in_feature.loc[[index],'geometry'] = fix # issue with Poly > Multipolygon
except ValueError:
in_feature.loc[[index],'geometry'] = in_feature.loc[[index], 'geometry'].buffer(0)
return in_feature