geoplanar icon indicating copy to clipboard operation
geoplanar copied to clipboard

Resolve overlapping polygons by merging them

Open martinfleis opened this issue 3 years ago • 1 comments

I am currently trying to use geoplanar to preprocess building footprints that have a ton of overlapping geometries. In some cases, trimming is not the right solution because the two polygons represent different height of parts of buildings. So the solution there is to merge then together.

I have a function that does that, would you be interested in a PR?

def merge_overlapping(gdf, merge_limit, overlap_limit, largest=None):
    """Merge overlapping polygons based on a set of conditions.
    
    Overlapping polygons smaller than ``merge_limit`` are merged to a neighboring polygon.
    If ``largest=None`` it picks one randomly, otherwise it picks the largest (True) or the
    smallest (False).
    
    Polygons larger than ``merge_limit`` are merged to neighboring if they share area larger
    than ``area * overlap_limit``.
    
    Parameters
    ----------
    gdf : GeoDataFrame
        GeoDataFrame with polygon or mutli polygon geometry
    merge_limit : float
        area of overlapping polygons that are to be merged with neighbors no matter the size
        of the overlap
    overlap_limit : float (0-1)
        ratio of area of an overlapping polygon that has to be shared with other polygon 
        to merge both into a one
    largest : bool (default None)
        Merge each overlapping polygons smaller than merge_limit with its largest (True), or smallest (False) neighbor.
        If None, merge with any neighbor non-deterministically but performantly.
        
    Returns
    -------

    GeoDataFrame
    """
    neighbors = {}
    for i, poly in tqdm(gdf.geometry.iteritems(), total=len(gdf)):
        hits = gdf.sindex.query(poly, predicate='intersects')
        hits = hits[hits != i]

        if hits.size == 0:
            neighbors[i] = []
            continue

        if poly.area < merge_limit:
            if hits.size == 0:
                neighbors[i] = list(hits)
            else:
                if largest is None:
                    neighbors[i] = [hits[0]]
                else:
                    sub = gdf.geometry.iloc[hits]
                    inters = sub.intersection(poly.exterior)
                    if largest:
                        neighbors[i] = [inters.length.idxmax()]
                    else:
                        neighbors[i] = [inters.length.idxmin()]
        else:
            sub = gdf.geometry.iloc[hits]
            inters = sub.intersection(poly)
            include = sub.index[inters.area > (sub.area * overlap_limit)]
            neighbors[i] = list(include)
    
    W = libpysal.weights.W(neighbors, silence_warnings=True)
    return gdf.dissolve(W.component_labels)

martinfleis avatar Aug 25 '22 11:08 martinfleis

This would definitely be a useful piece of functionality to have. +1 on a pr.

sjsrey avatar Aug 25 '22 13:08 sjsrey

Closed by #50

martinfleis avatar May 31 '24 15:05 martinfleis