@@ -1246,47 +1246,52 @@ def _get_linear_colormap(colors: list[str], background: str) -> list[LinearSegme
12461246 return [LinearSegmentedColormap .from_list (c , [background , c ], N = 256 ) for c in colors ]
12471247
12481248
1249- def _split_multipolygon_into_outer_and_inner (mp : shapely .MultiPolygon ): # type: ignore
1250- # https://stackoverflow.com/a/21922058
1251-
1252- for geom in mp .geoms :
1253- if geom .geom_type == "MultiPolygon" :
1254- exterior_coords = []
1255- interior_coords = []
1256- for part in geom :
1257- epc = _split_multipolygon_into_outer_and_inner (part ) # Recursive call
1258- exterior_coords += epc ["exterior_coords" ]
1259- interior_coords += epc ["interior_coords" ]
1260- elif geom .geom_type == "Polygon" :
1261- exterior_coords = geom .exterior .coords [:]
1262- interior_coords = []
1263- for interior in geom .interiors :
1264- interior_coords += interior .coords [:]
1249+ def _collect_polygon_rings (
1250+ geom : shapely .Polygon | shapely .MultiPolygon ,
1251+ ) -> list [tuple [np .ndarray , list [np .ndarray ]]]:
1252+ """Collect exterior/interior coordinate rings from (Multi)Polygons."""
1253+ polygons : list [tuple [np .ndarray , list [np .ndarray ]]] = []
1254+
1255+ def _collect (part : shapely .Polygon | shapely .MultiPolygon ) -> None :
1256+ if part .geom_type == "Polygon" :
1257+ exterior = np .asarray (part .exterior .coords )
1258+ interiors = [np .asarray (interior .coords ) for interior in part .interiors ]
1259+ polygons .append ((exterior , interiors ))
1260+ elif part .geom_type == "MultiPolygon" :
1261+ for child in part .geoms :
1262+ _collect (child )
12651263 else :
1266- raise ValueError (f"Unhandled geometry type: { repr (geom . type )} " )
1264+ raise ValueError (f"Unhandled geometry type: { repr (part . geom_type )} " )
12671265
1268- return interior_coords , exterior_coords
1266+ _collect (geom )
1267+ return polygons
1268+
1269+
1270+ def _create_ring_codes (length : int ) -> npt .NDArray [np .uint8 ]:
1271+ codes = np .full (length , mpath .Path .LINETO , dtype = mpath .Path .code_type )
1272+ codes [0 ] = mpath .Path .MOVETO
1273+ return codes
12691274
12701275
12711276def _make_patch_from_multipolygon (mp : shapely .MultiPolygon ) -> mpatches .PathPatch :
12721277 # https://matplotlib.org/stable/gallery/shapes_and_collections/donut.html
12731278
12741279 patches = []
1275- for geom in mp . geoms :
1276- if len (geom . interiors ) == 0 :
1277- # polygon has no holes
1278- patches += [ mpatches . Polygon ( geom . exterior . coords , closed = True )]
1279- else :
1280- inside , outside = _split_multipolygon_into_outer_and_inner ( mp )
1281- if len (inside ) > 0 :
1282- codes = np . ones ( len ( inside ), dtype = mpath . Path . code_type ) * mpath . Path . LINETO
1283- codes [ 0 ] = mpath . Path . MOVETO
1284- all_codes = np . concatenate (( codes , codes ) )
1285- vertices = np . concatenate (( outside , inside [:: - 1 ] ))
1286- else :
1287- all_codes = []
1288- vertices = np .concatenate (outside )
1289- patches += [ mpatches .PathPatch (mpath .Path (vertices , all_codes ))]
1280+ for exterior , interiors in _collect_polygon_rings ( mp ) :
1281+ if len (interiors ) == 0 :
1282+ patches . append ( mpatches . Polygon ( exterior , closed = True ))
1283+ continue
1284+
1285+ ring_vertices = [ exterior ]
1286+ ring_codes = [ _create_ring_codes ( len (exterior ))]
1287+ for hole in interiors :
1288+ reversed_hole = hole [:: - 1 ]
1289+ ring_vertices . append ( reversed_hole )
1290+ ring_codes . append ( _create_ring_codes ( len ( reversed_hole ) ))
1291+
1292+ vertices = np . concatenate ( ring_vertices )
1293+ all_codes = np .concatenate (ring_codes )
1294+ patches . append ( mpatches .PathPatch (mpath .Path (vertices , all_codes )))
12901295
12911296 return patches
12921297
0 commit comments