Skip to content

Commit 6716f75

Browse files
committed
other multipolygon type works now
1 parent dbc66de commit 6716f75

File tree

2 files changed

+51
-28
lines changed

2 files changed

+51
-28
lines changed

src/spatialdata_plot/pl/render.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
from __future__ import annotations
22

3+
import contextlib
34
from collections.abc import Sequence
45
from copy import copy
56
from dataclasses import dataclass
67
from functools import partial
8+
from itertools import chain
79
from typing import Any, Callable, Optional, Union
810

11+
import geopandas as gpd
912
import matplotlib
1013
import numpy as np
1114
import pandas as pd
@@ -119,9 +122,15 @@ def _get_collection_shape(
119122
outline_alpha: None | float = None,
120123
**kwargs: Any,
121124
) -> PatchCollection:
122-
polygon_df = shapes[shapes["geometry"].apply(lambda geom: geom.geom_type == "Polygon")]
123-
multipolygon_df = shapes[shapes["geometry"].apply(lambda geom: geom.geom_type == "MultiPolygon")]
124-
circle_df = shapes[shapes["geometry"].apply(lambda geom: geom.geom_type == "Point")]
125+
polygon_df = shapes[
126+
shapes["geometry"].apply(lambda geom: geom.geom_type == "Polygon") # type: ignore[call-overload]
127+
]
128+
multipolygon_df = shapes[
129+
shapes["geometry"].apply(lambda geom: geom.geom_type == "MultiPolygon") # type: ignore[call-overload]
130+
]
131+
circle_df = shapes[
132+
shapes["geometry"].apply(lambda geom: geom.geom_type == "Point") # type: ignore[call-overload]
133+
]
125134

126135
patches = []
127136
if len(polygon_df) > 0:
@@ -133,6 +142,10 @@ def _get_collection_shape(
133142
if len(multipolygon_df) > 0:
134143
patches += [_make_patch_from_multipolygon(mp) for mp in multipolygon_df["geometry"]]
135144

145+
# flatten list since multipolygons cause a nested list
146+
with contextlib.suppress(Exception):
147+
patches = list(chain.from_iterable([x] if not isinstance(x, list) else x for x in patches))
148+
136149
cmap = kwargs["cmap"]
137150

138151
try:
@@ -169,6 +182,8 @@ def _get_collection_shape(
169182
if len(color_vector) == 0:
170183
color_vector = [render_params.cmap_params.na_color]
171184

185+
shapes = pd.concat(shapes, ignore_index=True)
186+
shapes = gpd.GeoDataFrame(shapes, geometry="geometry")
172187
_cax = _get_collection_shape(
173188
shapes=shapes,
174189
s=render_params.size,

src/spatialdata_plot/pl/utils.py

Lines changed: 33 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1134,36 +1134,44 @@ def _robust_transform(element: Any, cs: str) -> Any:
11341134

11351135
def _split_multipolygon_into_outer_and_inner(mp: shapely.MultiPolygon): # type: ignore
11361136
# https://stackoverflow.com/a/21922058
1137-
if len(mp.geoms) > 1:
1138-
raise NotImplementedError("Currently, lists of Polygons are not supported. Only Polygons with holes.")
1139-
1140-
geom = mp.geoms[0]
1141-
if geom.type == "Polygon":
1142-
exterior_coords = geom.exterior.coords[:]
1143-
interior_coords = []
1144-
for interior in geom.interiors:
1145-
interior_coords += interior.coords[:]
1146-
elif geom.type == "MultiPolygon":
1147-
exterior_coords = []
1148-
interior_coords = []
1149-
for part in geom:
1150-
epc = _split_multipolygon_into_outer_and_inner(part) # Recursive call
1151-
exterior_coords += epc["exterior_coords"]
1152-
interior_coords += epc["interior_coords"]
1153-
else:
1154-
raise ValueError("Unhandled geometry type: " + repr(geom.type))
1137+
1138+
for geom in mp.geoms:
1139+
if geom.geom_type == "Polygon":
1140+
exterior_coords = geom.exterior.coords[:]
1141+
interior_coords = []
1142+
for interior in geom.interiors:
1143+
interior_coords += interior.coords[:]
1144+
elif geom.geom_type == "MultiPolygon":
1145+
exterior_coords = []
1146+
interior_coords = []
1147+
for part in geom:
1148+
epc = _split_multipolygon_into_outer_and_inner(part) # Recursive call
1149+
exterior_coords += epc["exterior_coords"]
1150+
interior_coords += epc["interior_coords"]
1151+
else:
1152+
raise ValueError("Unhandled geometry type: " + repr(geom.type))
11551153

11561154
return interior_coords, exterior_coords
11571155

11581156

11591157
def _make_patch_from_multipolygon(mp: shapely.MultiPolygon) -> mpatches.PathPatch:
11601158
# https://matplotlib.org/stable/gallery/shapes_and_collections/donut.html
11611159

1162-
inside, outside = _split_multipolygon_into_outer_and_inner(mp)
1163-
codes = np.ones(len(inside), dtype=mpath.Path.code_type) * mpath.Path.LINETO
1164-
codes[0] = mpath.Path.MOVETO
1165-
vertices = np.concatenate((outside, inside[::-1]))
1166-
all_codes = np.concatenate((codes, codes))
1167-
path = mpath.Path(vertices, all_codes)
1160+
patches = []
1161+
for geom in mp.geoms:
1162+
if len(geom.interiors) == 0:
1163+
# polygon has no holes
1164+
patches += [mpatches.Polygon(geom.exterior.coords, closed=True)]
1165+
else:
1166+
inside, outside = _split_multipolygon_into_outer_and_inner(mp)
1167+
if len(inside) > 0:
1168+
codes = np.ones(len(inside), dtype=mpath.Path.code_type) * mpath.Path.LINETO
1169+
codes[0] = mpath.Path.MOVETO
1170+
all_codes = np.concatenate((codes, codes))
1171+
vertices = np.concatenate((outside, inside[::-1]))
1172+
else:
1173+
all_codes = []
1174+
vertices = np.concatenate(outside)
1175+
patches += [mpatches.PathPatch(mpath.Path(vertices, all_codes))]
11681176

1169-
return mpatches.PathPatch(path)
1177+
return patches

0 commit comments

Comments
 (0)