43

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
  • 6,348
  • 7
  • 46
  • 58
jpcgt
  • 1,880
  • 2
  • 17
  • 31

2 Answers2

53

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
  • 6,348
  • 7
  • 46
  • 58
jpcgt
  • 1,880
  • 2
  • 17
  • 31
  • 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
  • 22
    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

I had a very similar issue with my geometries. So I first simplified the geometry with a very small tolerance. Then I checked for each line of adjoining coordinates in the exterior of my polygon if they intersect (or touch) another line. If they did, I spitted the polygon at the intersection point into two geometries. I did this recursively until each sub geometry of my polygon was either empty, a line or a valid Polygon.

Next, I filtered the result for polygons and clipped each polygon by all the other polygons in the set. What I got is a list of valid polygons with only 'inner' areas. At this point, if I had any known valid interiors, I clipped them too. At last, I build a unary_union() of all the remaining sub geometry pieces.

I your case, it resulted in:

Polygon(((0,0), (0,3), (3,3), (3,0)), ((1,1), (2,1), (2,2), (1,2)))

It's a single Polygon with an 1x1 interior.

Might be a bit computational intensive, but it work and fixes roughly any corrupt 2D Polygon.

magraf
  • 352
  • 2
  • 8