54

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:

enter image description here

>>> 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)?

Georgy
  • 12,464
  • 7
  • 65
  • 73
jpcgt
  • 2,138
  • 2
  • 19
  • 34

6 Answers6

58

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)]
Georgy
  • 12,464
  • 7
  • 65
  • 73
jpcgt
  • 2,138
  • 2
  • 19
  • 34
  • 1
    I find that this buffer( 0 ) method works in general for fixing polygons with coincident lines among sub-polygons. This is very useful. Do you know of a place where this trick is officially recommended/sanctioned? – M Katz Mar 23 '14 at 22:20
  • 2
    This method did not fix my issue: I had some self-intersecting multipolygons. – Rutger Hofste Sep 15 '17 at 16:35
  • 29
    You need to be a little careful with the `buffer(0)` technique. We've had bowtie cases where it destroyed the big part of the polygon and left just a small bowtied corner. YMMV. – Aidan Kane Dec 06 '17 at 11:31
  • For me it made a valid polygon but it no longer had any .exterior.coords so it still did not work for a .within(polygon) analysis. – Casivio Feb 12 '21 at 20:29
  • 1
    Does anyone knows if a solution exists for this now? As much as i undertand @AidanKane didn't recommended the `buffer(0)`. – José Rojas Jul 03 '21 at 07:08
  • @JoséRojas It's.....complicated. The solution we ended up using is to break invalid polygons into a map of valid polygons using shapely.ops.polygonize. We then iterated of each one and decided if it was inside or outside using winding rules. We then union the included ones together. – Aidan Kane Jul 08 '21 at 23:54
  • @JoséRojas See my new answer, looks like shapely have added a function to do this! https://shapely.readthedocs.io/en/latest/manual.html#validation.make_valid – Aidan Kane Jul 08 '21 at 23:58
  • buffer(0) also behaves different on different os (see my answer)! – LegNaiB Oct 03 '21 at 16:56
11

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))
Michael Delgado
  • 13,789
  • 3
  • 29
  • 54
Rafael Matias
  • 111
  • 1
  • 3
  • 3
    This doesn't work, the following exception is raised: AttributeError: 'LGEOS360' object has no attribute 'GEOSMakeValid' Any objections, how to solve this? I would like to use the make_valid function but on a stable release (and no alpha release) ... – LegNaiB Oct 01 '21 at 15:18
  • make_valid is better than a buffer(0) which can change the geometry completely. The the doc's [second example](https://shapely.readthedocs.io/en/latest/manual.html#id32) in particular is a good example to test both methods. – tgrandje Mar 07 '23 at 13:27
4

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

Aidan Kane
  • 3,856
  • 2
  • 25
  • 28
3

simplest solution

new_polygon = pp.buffer(0)
new_polygon.is_valid

maybe it would be work.

minsu
  • 81
  • 5
2

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).

LegNaiB
  • 121
  • 2
1

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
mmann1123
  • 5,031
  • 7
  • 41
  • 49