Source code for voxcity.geoprocessor.overlap
"""
Utilities for processing overlaps between building footprints.
"""
from rtree import index
from shapely.errors import GEOSException
[docs]
def process_building_footprints_by_overlap(filtered_gdf, overlap_threshold=0.5):
"""
Merge overlapping buildings based on area overlap ratio, assigning the ID of the larger building
to smaller overlapping ones.
"""
gdf = filtered_gdf.copy()
if 'id' not in gdf.columns:
gdf['id'] = gdf.index
if gdf.crs is None:
gdf_projected = gdf.copy()
else:
gdf_projected = gdf.to_crs("EPSG:3857")
gdf_projected['area'] = gdf_projected.geometry.area
gdf_projected = gdf_projected.sort_values(by='area', ascending=False)
gdf_projected = gdf_projected.reset_index(drop=True)
spatial_idx = index.Index()
for i, geom in enumerate(gdf_projected.geometry):
if geom.is_valid:
spatial_idx.insert(i, geom.bounds)
else:
fixed_geom = geom.buffer(0)
if fixed_geom.is_valid:
spatial_idx.insert(i, fixed_geom.bounds)
id_mapping = {}
for i in range(1, len(gdf_projected)):
current_poly = gdf_projected.iloc[i].geometry
current_area = gdf_projected.iloc[i].area
current_id = gdf_projected.iloc[i]['id']
if current_id in id_mapping:
continue
if not current_poly.is_valid:
current_poly = current_poly.buffer(0)
if not current_poly.is_valid:
continue
potential_overlaps = [j for j in spatial_idx.intersection(current_poly.bounds) if j < i]
for j in potential_overlaps:
larger_poly = gdf_projected.iloc[j].geometry
larger_id = gdf_projected.iloc[j]['id']
if larger_id in id_mapping:
larger_id = id_mapping[larger_id]
if not larger_poly.is_valid:
larger_poly = larger_poly.buffer(0)
if not larger_poly.is_valid:
continue
try:
if current_poly.intersects(larger_poly):
overlap = current_poly.intersection(larger_poly)
overlap_ratio = overlap.area / current_area
if overlap_ratio > overlap_threshold:
id_mapping[current_id] = larger_id
gdf_projected.at[i, 'id'] = larger_id
break
except (GEOSException, ValueError):
continue
for i, row in filtered_gdf.iterrows():
orig_id = row.get('id')
if orig_id in id_mapping:
filtered_gdf.at[i, 'id'] = id_mapping[orig_id]
return filtered_gdf