Subtracting polygons and converting them to not have holes in python

66 views Asked by At

I have several blue and red polygons. I would like the red ones subtracted from the blue ones.

After this is done, some remaining polygons may have holes. I'd like those polygons with holes converted to polygons without holes.

What I've tried so far

from typing import TypedDict
from shapely.geometry import MultiPolygon, Polygon


class Coords(TypedDict):
    x: list[float]
    y: list[float]


def subtract_polygons(group_a: list[Coords], group_b: list[Coords]):
    # Convert polygons to Shapely Polygon objects
    polygons_a = [Polygon(zip(group["x"], group["y"])) for group in group_a]
    polygons_b = [Polygon(zip(group["x"], group["y"])) for group in group_b]

    # Create a "negative" polygon for the hole in group B
    negative_polygons_b = [polygon.exterior for polygon in polygons_b]

    # Subtract each polygon in polygons_b from each polygon in polygons_a
    result_polygons = []
    for polygon_a in polygons_a:
        for negative_polygon_b in negative_polygons_b:
            result_polygon = polygon_a.difference(negative_polygon_b)
            result_polygons.append(result_polygon)

    # Convert the resulting geometry to MultiPolygon
    result_multipolygon = MultiPolygon(result_polygons)

    print("polygons_a", polygons_a)
    print("polygons_b", polygons_b)
    print("negative_polygons_b", negative_polygons_b)
    print("result_multipolygon", result_multipolygon)

group_a: list[Coords] = [
    {"x": [100, 200, 200, 100, 100], "y": [100, 100, 200, 200, 100]},
    {"x": [130, 230, 230, 130, 130], "y": [130, 130, 230, 230, 130]},
    {"x": [180, 280, 280, 180, 180], "y": [180, 180, 280, 280, 180]},
]
group_b: list[Coords] = [
    {"x": [150, 175, 175, 150, 150], "y": [150, 150, 175, 175, 150]},
    {"x": [150, 250, 250, 150, 150], "y": [220, 220, 320, 320, 220]},
]

subtract_polygons(group_a, group_b)

Desired result:

Note that the little line is arbitrary. I just wanted to show that it's a single polygon, concave in this case, without a hole

enter image description here

Input and Actual Result:

enter image description here

The console output:

polygons_a [
<POLYGON ((100 100, 200 100, 200 200, 100 200, 100 100))>,
<POLYGON ((130 130, 230 130, 230 230, 130 230, 130 130))>,
<POLYGON ((180 180, 280 180, 280 280, 180 280, 180 180))>
]

polygons_b [
<POLYGON ((150 150, 175 150, 175 175, 150 175, 150 150))>,
<POLYGON ((150 220, 250 220, 250 320, 150 320, 150 220))>
]

negative_polygons_b [
<LINEARRING (150 150, 175 150, 175 175, 150 175, 150 150)>,
<LINEARRING (150 220, 250 220, 250 320, 150 320, 150 220)>
]

result_multipolygon MULTIPOLYGON (
((100 200, 200 200, 200 100, 100 100, 100 200)),
((100 200, 200 200, 200 100, 100 100, 100 200)),
((130 230, 230 230, 230 130, 130 130, 130 230)),
((230 130, 130 130, 130 230, 150 230, 230 230, 230 220, 230 130)),
((180 280, 280 280, 280 180, 180 180, 180 280)),
((280 280, 280 180, 180 180, 180 220, 180 280, 250 280, 280 280))
)
1

There are 1 answers

0
Pieter On

If I understand correctly what you want to accomplish, there were several problems in your script:

  • polygon.exterior returns a linearring, not a Polygon, so you cannot use that directly to difference with
  • you want to subtract each polygon in group b from each polygon from group a, but your loops didn't do this
  • based on you sample output, you want the output to be unioned

Corrected script:

from typing import TypedDict
from matplotlib import pyplot as plt
import shapely
from shapely.geometry import Polygon
from shapely.plotting import plot_polygon


class Coords(TypedDict):
    x: list[float]
    y: list[float]


def subtract_polygons(group_a: list[Coords], group_b: list[Coords]):
    # Convert polygons to Shapely Polygon objects
    polygons_a = [Polygon(zip(group["x"], group["y"])) for group in group_a]
    polygons_b = [Polygon(zip(group["x"], group["y"])) for group in group_b]

    # Create a "negative" polygon for the hole in group B
    negative_polygons_b = [Polygon(polygon.exterior) for polygon in polygons_b]

    # Subtract each polygon in polygons_b from each polygon in polygons_a
    result_polygons = []
    for polygon_a in polygons_a:
        result_polygon = polygon_a
        for negative_polygon_b in negative_polygons_b:
            result_polygon = result_polygon.difference(negative_polygon_b)

        result_polygons.append(result_polygon)

    return shapely.union_all(result_polygons)


group_a: list[Coords] = [
    {"x": [100, 200, 200, 100, 100], "y": [100, 100, 200, 200, 100]},
    {"x": [130, 230, 230, 130, 130], "y": [130, 130, 230, 230, 130]},
    {"x": [180, 280, 280, 180, 180], "y": [180, 180, 280, 280, 180]},
]
group_b: list[Coords] = [
    {"x": [150, 175, 175, 150, 150], "y": [150, 150, 175, 175, 150]},
    {"x": [150, 250, 250, 150, 150], "y": [220, 220, 320, 320, 220]},
]

result = subtract_polygons(group_a, group_b)

# Plot
polygons_a = [Polygon(zip(group["x"], group["y"])) for group in group_a]
polygons_b = [Polygon(zip(group["x"], group["y"])) for group in group_b]

for polygon_a in polygons_a:
    plot_polygon(polygon_a)
for polygon_b in polygons_b:
    plot_polygon(polygon_b, color="red")
plt.show()
plot_polygon(result)
plt.show()

Result:

enter image description here