__all__ = ['optimizealpha'] import sys import logging import shapely from shapely.geometry import MultiPoint import numpy try: import geopandas USE_GP = True except ImportError: USE_GP = False def _testalpha(points, alpha): """ Evaluates an alpha parameter. This helper function creates an alpha shape with the given points and alpha parameter. It then checks that the produced shape is a Polygon and that it intersects all the input points. Args: points (``shapely.geometry.Multipoint``): data points alpha (float): alpha value Returns: bool: True if the resulting alpha shape is a single polygon that intersects all the input data points. """ try: from alphashape import alphashape except ImportError: from .alphashape import alphashape polygon = alphashape(points, alpha) if isinstance(polygon, shapely.geometry.polygon.Polygon) and all( [polygon.intersects(point) for point in points]): return True else: return False def optimizealpha(points, max_iterations=10000): """ Solve for the alpha parameter. Attempt to determine the alpha parameter that best wraps the given set of points in one polygon without dropping any points. Note: If the solver fails to find a solution, a value of zero will be returned, which when used with the alphashape function will safely return a convex hull around the points. Args: points (list): an iterable container of points max_iterations (int): maximum number of iterations while finding the solution Returns: float: The optimized alpha parameter """ # Convert to a shapely multipoint object if not one already if USE_GP and isinstance(points, geopandas.GeoDataFrame): points = points['geometry'] if not isinstance(points, MultiPoint): points = MultiPoint(list(points)) # Set the bounds lower = 0. # Ensure the upper limit bounds the solution upper = sys.float_info.max if _testalpha(points, upper): logging.error('the max float value does not bound the alpha ' 'parameter solution') return 0. # Begin the bisection loop counter = 0 while (upper - lower) > numpy.finfo(float).eps * 2: # Bisect the current bounds test_alpha = (upper + lower) * .5 # Update the bounds to include the solution space if _testalpha(points, test_alpha): lower = test_alpha else: upper = test_alpha # Handle exceeding maximum allowed number of iterations counter += 1 if counter > max_iterations: logging.warning('maximum allowed iterations reached while ' 'optimizing the alpha parameter') break return lower