Bowtie or hourglass polygon validity issue when self-crossing point is not defined

Using Python2.7 and shapely, let:

import shapely
coords = [(0, 0), (0, 2), (2, 0), (2, 2), (0, 0)]
bowtie = shapely.geometry.Polygon(coords)
bowtie.is_valid

which gives this :

Self-intersection at or near point 1 1
Out: False

So let try the buffer(0) method as describer in the doc (http://toblerity.org/shapely/manual.html):

clean = bowtie.buffer(0)
type(clean)

which gives that:

Out: shapely.geometry.polygon.Polygon

The difference between the example in the doc is that in the present case, the crossing point is not defined in the original coordinates, so it's not resulting in a multipolygon and it's not really consistent with these initial coordinates, as this command:

clean.wkt

shows when it returns:

Out: 'POLYGON ((0 0, 0 2, 1 1, 0 0))'

And the resulting image for a more visual understanding: With:
Red: original self-intersecting polygon
Teal: cleaned bowtie polygon with buffer(0) method (only one half of the original polygon. Take care of the different scaling effect on iPython...)

Question:
How to easily get the original shape back with a valid geometry so that we can perform further operations like intersections, etc?

• It seems that using slightly bigger buffer than zero has solved same kind of problems. – user30184 Jun 7 '17 at 19:32
• I've also tried in this example with buffer(0.1) to buffer(0.5) by increment 0.1; result is the same: clean is a simple polygon, not a multipolygon. – s.k Jun 7 '17 at 19:36
• You can use GeometryCollection((bowtie, clean)) to plot both at once. – bugmenot123 Jun 10 '17 at 14:26

A different hack would be using only the exterior of the bowtie, intersecting it with itself (generating a MultiLineString), polygonizing that (generating Polygons) and finally aggregating into a MultiPolygon:

be = bowtie.exterior
mls = be.intersection(be)
polygons = polygonize(mls)
valid_bowtie = MultiPolygon(polygons)
print(valid_bowtie)

MULTIPOLYGON (((0 0, 0 2, 1 1, 0 0)), ((2 0, 1 1, 2 2, 2 0)))

In my case, exterior was returning LinearRing, and anyway, intersection with itself didn't work because the original polygon isn't valid. I ended up finding this answer which was more helpful.

This is the code I came up with:

coords = [[0,0],[1,1],[0,1],[1,0],[0,0]]
line_non_simple = shapely.geometry.LineString(coords)
mls = shapely.ops.unary_union(line_non_simple)
polygons = list(shapely.ops.polygonize(mls))
shapely.geometry.MultiPolygon(polygons)