Skip to content

Commit 64aeb73

Browse files
authored
Polygons with holes are now rendered correctly (#513)
1 parent 35b5e20 commit 64aeb73

File tree

4 files changed

+49
-0
lines changed

4 files changed

+49
-0
lines changed

src/spatialdata_plot/pl/render.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
_prepare_transformation,
5656
_rasterize_if_necessary,
5757
_set_color_source_vec,
58+
_validate_polygons,
5859
)
5960

6061
_Normalize = Normalize | abc.Sequence[Normalize]
@@ -183,6 +184,8 @@ def _render_shapes(
183184
)
184185
shapes = _convert_shapes(shapes, render_params.shape, max_extent)
185186

187+
shapes = _validate_polygons(shapes)
188+
186189
# Determine which method to use for rendering
187190
method = render_params.method
188191

src/spatialdata_plot/pl/utils.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1545,6 +1545,38 @@ def _get_linear_colormap(colors: list[str], background: str) -> list[LinearSegme
15451545
return [LinearSegmentedColormap.from_list(c, [background, c], N=256) for c in colors]
15461546

15471547

1548+
def _validate_polygons(shapes: GeoDataFrame) -> GeoDataFrame:
1549+
"""
1550+
Convert Polygons with holes to MultiPolygons to keep interior rings during rendering.
1551+
1552+
Parameters
1553+
----------
1554+
shapes
1555+
GeoDataFrame containing a `geometry` column.
1556+
1557+
Returns
1558+
-------
1559+
GeoDataFrame
1560+
``shapes`` with holed Polygons converted to MultiPolygons.
1561+
"""
1562+
if "geometry" not in shapes:
1563+
return shapes
1564+
1565+
converted_count = 0
1566+
for idx, geom in shapes["geometry"].items():
1567+
if isinstance(geom, shapely.Polygon) and len(geom.interiors) > 0:
1568+
shapes.at[idx, "geometry"] = shapely.MultiPolygon([geom])
1569+
converted_count += 1
1570+
1571+
if converted_count > 0:
1572+
logger.info(
1573+
"Converted %d Polygon(s) with holes to MultiPolygon(s) for correct rendering.",
1574+
converted_count,
1575+
)
1576+
1577+
return shapes
1578+
1579+
15481580
def _collect_polygon_rings(
15491581
geom: shapely.Polygon | shapely.MultiPolygon,
15501582
) -> list[tuple[np.ndarray, list[np.ndarray]]]:
7.33 KB
Loading

tests/pl/test_render_shapes.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,20 @@ def test_plot_can_render_multipolygons_with_multiple_holes(self):
116116

117117
fig.tight_layout()
118118

119+
def test_plot_can_render_multipolygons_that_say_they_are_polygons(self):
120+
exterior = [(0, 0), (0, 1), (1, 1), (1, 0), (0, 0)]
121+
interior = [(0.1, 0.1), (0.1, 0.9), (0.9, 0.9), (0.9, 0.1), (0.1, 0.1)]
122+
polygon = Polygon(exterior, [interior])
123+
geo_df = gpd.GeoDataFrame(geometry=[polygon])
124+
sdata = SpatialData(shapes={"test": ShapesModel.parse(geo_df)})
125+
126+
fig, ax = plt.subplots()
127+
sdata.pl.render_shapes(element="test").pl.show(ax=ax)
128+
ax.set_xlim(-1, 2)
129+
ax.set_ylim(-1, 2)
130+
131+
fig.tight_layout()
132+
119133
def test_plot_can_color_multipolygons_with_multiple_holes(self):
120134
square = [(0.0, 0.0), (5.0, 0.0), (5.0, 5.0), (0.0, 5.0), (0.0, 0.0)]
121135
first_hole = [(1.0, 1.0), (2.0, 1.0), (2.0, 2.0), (1.0, 2.0), (1.0, 1.0)]

0 commit comments

Comments
 (0)