55import pandas as pd
66import shapely
77from packaging .version import Version
8+ from collections import defaultdict
9+ from esda .shape import isoperimetric_quotient
10+
811
912__all__ = ["gaps" , "fill_gaps" , "snap" ]
1013
@@ -57,51 +60,38 @@ def gaps(gdf):
5760 return polygons .drop (poly_idx ).reset_index (drop = True )
5861
5962
60- def fill_gaps (gdf , gap_df = None , largest = True , inplace = False ):
61- """Fill any gaps in a geodataframe .
63+ def fill_gaps (gdf , gap_df = None , strategy = 'largest' , inplace = False ):
64+ """Fill gaps in a GeoDataFrame by merging them with neighboring polygons .
6265
6366 Parameters
6467 ----------
65-
66- gdf : GeoDataFrame with polygon (multipolygon) GeoSeries
67-
68-
69- gap_df: GeoDataFrame with gaps to fill
70- If None, gaps will be determined
71-
72- largest: boolean (Default: True)
73- Merge each gap with its largest (True), or smallest (False) neighbor.
74- If None, merge with any neighbor non-deterministically but performantly.
75-
76- inplace: boolean (default: False)
77- Change the geoseries of current dataframe
78-
68+ gdf : GeoDataFrame
69+ A GeoDataFrame containing polygon or multipolygon geometries.
70+
71+ gap_df : GeoDataFrame, optional
72+ A GeoDataFrame containing the gaps to be filled. If None, gaps will be
73+ automatically detected within `gdf`.
74+
75+ strategy : {'smallest', 'largest', 'compact', None}, default 'largest'
76+ Strategy to determine how gaps are merged with neighboring polygons:
77+ - 'smallest': Merge each gap with the smallest neighboring polygon.
78+ - 'largest' : Merge each gap with the largest neighboring polygon.
79+ - 'compact' : Merge each gap with the neighboring polygon that results in
80+ the new polygon having the highest compactness
81+ (isoperimetric quotient).
82+ - None : Merge each gap with the first available neighboring polygon
83+ (order is indeterminate).
84+
85+ inplace : bool, default False
86+ If True, modify the input GeoDataFrame in place. Otherwise, return a new
87+ GeoDataFrame with the gaps filled.
7988
8089 Returns
8190 -------
82-
83- _gaps : GeoDataFrame with gap polygons
84-
85- Examples
86- --------
87- >>> p1 = box(0,0,10,10)
88- >>> p2 = Polygon([(10,10), (12,8), (10,6), (12,4), (10,2), (20,5)])
89- >>> gdf = geopandas.GeoDataFrame(geometry=[p1,p2])
90- >>> gdf.area
91- 0 100.0
92- 1 32.0
93- dtype: float64
94- >>> h = geoplanar.gaps(gdf)
95- >>> h.area
96- array([4., 4.])
97- >>> gdf1 = geoplanar.fill_gaps(gdf)
98- >>> gdf1.area
99- 0 108.0
100- 1 32.0
101- dtype: float64
91+ GeoDataFrame or None
92+ A new GeoDataFrame with gaps filled if `inplace` is False. Otherwise,
93+ modifies `gdf` in place and returns None.
10294 """
103- from collections import defaultdict
104-
10595 if gap_df is None :
10696 gap_df = gaps (gdf )
10797
@@ -116,13 +106,32 @@ def fill_gaps(gdf, gap_df=None, largest=True, inplace=False):
116106 gap_idx , gdf_idx = gdf .sindex .query (gap_df .geometry , predicate = "intersects" )
117107
118108 to_merge = defaultdict (set )
109+
119110 for g_ix in range (len (gap_df )):
120111 neighbors = gdf_idx [gap_idx == g_ix ]
121- if largest is None : # don't care which polygon is a gap attached to
112+
113+ if strategy == 'compact' :
114+ # Find the neighbor that results in the highest IQ
115+ gap_geom = shapely .make_valid (gap_df .geometry .iloc [g_ix ])
116+ best_iq = - 1
117+ best_neighbor = None
118+ neighbor_geometries = gdf .geometry .iloc [neighbors ].apply (shapely .make_valid )
119+ for neighbor , neighbor_geom in zip (neighbors , neighbor_geometries ):
120+ combined_geom = shapely .union_all (
121+ [neighbor_geom , gap_geom ]
122+ )
123+ iq = isoperimetric_quotient (combined_geom )
124+ if iq > best_iq :
125+ best_iq = iq
126+ best_neighbor = neighbor
127+ to_merge [best_neighbor ].add (g_ix )
128+ elif strategy is None : # don't care which polygon we attach cap to
122129 to_merge [gdf .index [neighbors [0 ]]].add (g_ix )
123- elif largest :
130+ elif strategy == 'largest' :
131+ # Attach to the largest neighbor
124132 to_merge [gdf .iloc [neighbors ].area .idxmax ()].add (g_ix )
125133 else :
134+ # Attach to the smallest neighbor
126135 to_merge [gdf .iloc [neighbors ].area .idxmin ()].add (g_ix )
127136
128137 new_geom = []
0 commit comments