When working with geometric objects I like working with the shapely module (Shapely - PyPI). In here it is easy to create polygons and define union, intersection and differences. Like so:
from shapely.geometry import Polygon
x1 = [3, 3, 6, 6, 3]
y1 = [4, 8, 8, 4, 4]
x2 = [1, 8, 8, 1, 1]
y2 = [1, 1, 6, 6, 1]
rectangle_1 = Polygon([*zip(x1, y1)])
rectangle_2 = Polygon([*zip(x2, y2)])
intersection = rectangle_1.intersection(rectangle_2)
union = rectangle_1.union(rectangle_2)
difference_1_2 = rectangle_1.difference(rectangle_2)
difference_2_1 = rectangle_2.difference(rectangle_1)
However, when plotting this polygons in Matplotlib I could not find a direct method where I can plot the exterior and interior paths that can exist in shapely Polygons. In particular being able to plot 'holes' in a bigger polygon created by differences of smaller polygons fully embedded in the bigger one.
I came up with the following solution whereby the exterior and interior paths of the polygon are drawn using the the mpl Path module and convert this to a patch.
import matplotlib.pyplot as plt
from matplotlib.path import Path
from matplotlib.patches import PathPatch
from shapely.geometry import Polygon
class PatchPolygon:
def __init__(self, polygon, **kwargs):
polygon_path = self.pathify(polygon)
self._patch = PathPatch(polygon_path, **kwargs)
@property
def patch(self):
return self._patch
@staticmethod
def pathify(polygon):
''' Convert coordinates to path vertices. Objects produced by Shapely's
analytic methods have the proper coordinate order, no need to sort.
The codes will be all "LINETO" commands, except for "MOVETO"s at the
beginning of each subpath
'''
vertices = list(polygon.exterior.coords)
codes = [Path.MOVETO if i == 0 else Path.LINETO
for i in range(len(polygon.exterior.coords))]
for interior in polygon.interiors:
vertices += list(interior.coords)
codes += [Path.MOVETO if i == 0 else Path.LINETO
for i in range(len(interior.coords))]
return Path(vertices, codes)
x1 = [3, 6, 6, 3]
y1 = [2, 2, 4, 4]
x2 = [1, 1, 2, 2]
y2 = [1, 2, 2, 1]
x3 = [0, 9, 9, 0]
y3 = [0, 0, 6, 6]
fig, ax1 = plt.subplots(figsize=(6, 4))
ax1.set_xlim(-1, 10)
ax1.set_ylim(-1, 10)
rectangle_1 = Polygon([*zip(x1, y1)])
rectangle_2 = Polygon([*zip(x2, y2)])
rectangle_3 = Polygon([*zip(x3, y3)])
difference = rectangle_3.difference(rectangle_2).difference(rectangle_1)
ax1.add_patch(PatchPolygon(difference, facecolor='blue', edgecolor='red').patch)
plt.show()
Credit goes to Sean Gillies (painting-punctured-polygons-with-matplotlib) who inspired me to the solution. However we are now almost 10 years further, so wondered if there is a better way!