def naive_compute_iou_matrix(sorted_detections, ground_truths): """Computes the iou scores between all pairs of geometries in a naive fashion. Args: sorted_detections (ndarray, list) : A ndarray of detections stored as: * Bounding boxes for a given class where each row is a detection stored as: ``[BoundingBox, confidence]`` * Polygons for a given class where each row is a detection stored as: ``[Polygon, confidence]`` * Points for a given class where each row is a detection stored as: ``[Point, confidence]`` ground_truths (ndarray,list) : A ndarray of ground truth stored as: * Bounding boxes for a given class where each row is a ground truth stored as: ``[BoundingBox]`` * Polygons for a given class where each row is a ground truth stored as: ``[Polygon]`` * Points for a given class where each row is a ground truth stored as: ``[Point]`` Returns: ndarray : An IoU matrix (#detections, #ground truth) """ # We prepare the IoU matrix (#detection, #gt) iou = np.zeros((sorted_detections.shape[0], len(ground_truths))) # Naive iterative IoU matrix construction (Note: we iterate over the sorted detections) for k, ground_truth in enumerate(ground_truths): for m, detection in enumerate(sorted_detections): iou[m, k] = area(intersection(detection[0], ground_truth[0])) / area(union(detection[0], ground_truth[0])) return iou
def _tess( self, ix, enclosure, buildings, query_inp, query_res, threshold, unique_id, **kwargs, ): poly = enclosure.values.data[ix] blg = buildings.iloc[query_res[query_inp == ix]] within = blg[ pygeos.area(pygeos.intersection(blg.geometry.values.data, poly)) > (pygeos.area(blg.geometry.values.data) * threshold)] if len(within) > 1: tess = self._morphological_tessellation( within, unique_id, poly, shrink=self.shrink, segment=self.segment, verbose=False, check=False, ) tess[self.enclosure_id] = ix return tess return gpd.GeoDataFrame({ self.enclosure_id: ix, unique_id: None }, geometry=[poly], index=[0])
def convex_hull_ratio(collection): """ ratio of the area of the convex hull to the area of the shape itself Altman's A_3 measure, from Neimi et al 1991. """ ga = _cast(collection) return pygeos.area(ga) / pygeos.area(pygeos.convex_hull(ga))
def remove_tiny_shapes(x, regionalized=False): """This function will remove the small shapes of multipolygons. Will reduce the size of the file. Arguments: *x* : a geometry feature (Polygon) to simplify. Countries which are very large will see larger (unhabitated) islands being removed. Optional Arguments: *regionalized* : Default is **False**. Set to **True** will use lower threshold settings (default: **False**). Returns: *MultiPolygon* : a shapely geometry MultiPolygon without tiny shapes. """ # if its a single polygon, just return the polygon geometry if pygeos.geometry.get_type_id(x.geometry) == 3: # 'Polygon': return x.geometry # if its a multipolygon, we start trying to simplify and remove shapes if its too big. elif pygeos.geometry.get_type_id(x.geometry) == 6: # 'MultiPolygon': if regionalized == False: area1 = 0.1 area2 = 250 elif regionalized == True: area1 = 0.01 area2 = 50 # dont remove shapes if total area is already very small if pygeos.area(x.geometry) < area1: return x.geometry # remove bigger shapes if country is really big if x['GID_0'] in ['CHL', 'IDN']: threshold = 0.01 elif x['GID_0'] in ['RUS', 'GRL', 'CAN', 'USA']: if regionalized == True: threshold = 0.01 else: threshold = 0.01 elif pygeos.area(x.geometry) > area2: threshold = 0.1 else: threshold = 0.001 # save remaining polygons as new multipolygon for the specific country new_geom = [] for index_ in range(pygeos.geometry.get_num_geometries(x.geometry)): if pygeos.area(pygeos.geometry.get_geometry(x.geometry, index_)) > threshold: new_geom.append( pygeos.geometry.get_geometry(x.geometry, index_)) return pygeos.creation.multipolygons(numpy.array(new_geom))
def minimum_bounding_circle_ratio(collection): """ The Reock compactness measure, defined by the ratio of areas between the minimum bounding/containing circle of a shape and the shape itself. Measure A1 in Altman (1998), cited for Frolov (1974), but earlier from Reock (1963) """ ga = _cast(collection) mbc = pygeos.minimum_bounding_circle(ga) return pygeos.area(ga) / pygeos.area(mbc)
def external_entropy(a, b, balance=0, base=numpy.e): """ The harmonic mean summarizing the overlay entropy of two sets of polygons: a onto b and b onto a. Called the v-measure in :cite:`nowosad2018` Arguments ---------- a : geometry array of polygons array of polygons b : geometry array of polygons array of polygons balance: float weight that describing the relative importance of pattern a or pattern b. When large and positive, we weight the measure more to ensure polygons in b are fully contained by polygons in a. When large and negative, we weight the pattern to ensure polygons in A are fully contained by polygons in b. Corresponds to the log of beta in {cite}`Nowosad2018`. base: float base of logarithm to use throughout computation Returns -------- (n,) array expressing the entropy of the areal distributions of a's splits by partition b. Example ------- >>> r1 = geopandas.read_file('tests/regions.zip', layer='regions1') >>> r2 = geopandas.read_file('tests/regions.zip', layer='regions2') >>> external_entropy(r1, r2) """ a = _cast(a) b = _cast(b) beta = numpy.exp(balance) aix, bix, ab = _overlay(a, b, return_indices=True) a_areas = pygeos.area(a) b_areas = pygeos.area(b) ab_areas = pygeos.area(ab) b_onto_a = _overlay_entropy(aix, a_areas, ab_areas, base=base) # SjZ # SZ, as sabre has entropy.empirical(rowSums(xtab), unit='log2') b_onto_a /= areal_entropy(areas=b_areas, local=False, base=base) a_onto_b = _overlay_entropy(bix, b_areas, ab_areas, base=base) # SjR # SR, as sabre has entropy.empirical(colSums(xtab), unit='log2') a_onto_b /= areal_entropy(areas=a_areas, local=False, base=base) c = 1 - numpy.average(b_onto_a, weights=a_areas) h = 1 - numpy.average(a_onto_b, weights=b_areas) return (1 + beta) * h * c / ((beta * h) + c)
def area(gdf, km=True): """[summary] Args: gdf ([type]): [description] km (bool, optional): [description]. Defaults to True. Returns: [type]: [description] """ if km: return pygeos.area(convert_crs(gdf)[0]) / 1e6 else: return pygeos.area(convert_crs(gdf)[0])
def isoperimetric_quotient(collection): """ The Isoperimetric quotient, defined as the ratio of a polygon's area to the area of the equi-perimeter circle. Altman's PA_1 measure Construction: -------------- let: p_d = perimeter of polygon a_d = area of polygon a_c = area of the constructed circle r = radius of constructed circle then the relationship between the constructed radius and the polygon perimeter is: p_d = 2 \pi r p_d / (2 \pi) = r meaning the area of the circle can be expressed as: a_c = \pi r^2 a_c = \pi (p_d / (2\pi))^2 implying finally that the IPQ is: pp = (a_d) / (a_c) = (a_d) / ((p_d / (2*\pi))^2 * \pi) = (a_d) / (p_d**2 / (4\PI)) """ ga = _cast(collection) return (4 * numpy.pi * pygeos.area(ga)) / (pygeos.measurement.length(ga)** 2)
def summarize_by_huc12(units_df): print("Calculating overlap with land ownership and protection") ownership = gp.read_feather( ownership_filename, columns=["geometry", "FEE_ORGTYP", "GAP_STATUS"]) index_name = units_df.index.name df = intersection(units_df, ownership) if not len(df): return df["acres"] = pg.area(df.geometry_right.values.data) * M2_ACRES # drop areas that touch but have no overlap df = df.loc[df.acres > 0].copy() by_owner = (df[["FEE_ORGTYP", "acres"]].groupby( [index_name, "FEE_ORGTYP"]).acres.sum().astype("float32").round().reset_index()) by_protection = (df[["GAP_STATUS", "acres"]].groupby( [index_name, "GAP_STATUS"]).acres.sum().astype("float32").round().reset_index()) by_owner.to_feather(ownership_results_filename) by_protection.to_feather(protection_results_filename)
def summarize_by_huc12(units_df): """Calculate spatial join with counties Parameters ---------- df : GeoDataFrame summary units """ print("Calculating overlap with PARCAs") parca = gp.read_feather(parca_filename) df = intersection(units_df, parca) df["acres"] = pg.area(df.geometry_right.values.data) * M2_ACRES # drop areas that touch but have no overlap df = df.loc[df.acres > 0].copy() # aggregate these back up by ID by_parca = (df[[ "parca_id", "name", "description", "acres" ]].groupby(by=[df.index.get_level_values(0), "parca_id"]).agg({ "name": "first", "description": "first", "acres": "sum" }).reset_index().rename(columns={"level_0": "id"})) by_parca.acres = by_parca.acres.astype("float32").round() by_parca.to_feather(results_filename)
def create_final_od_grid(df,height_div): height = numpy.sqrt(pygeos.area(df.geometry)/height_div).values[0] grid = pd.DataFrame(create_grid(create_bbox(df),height),columns=['geometry']) #clip grid of bbox to grid of the actual spatial exterior of the country clip_grid = pygeos.intersection(grid,df.geometry) clip_grid = clip_grid.loc[~pygeos.is_empty(clip_grid.geometry)] # turn to shapely geometries again for zonal stats clip_grid.geometry = pygeos.to_wkb(clip_grid.geometry) clip_grid.geometry = clip_grid.geometry.apply(loads) clip_grid = gpd.GeoDataFrame(clip_grid) # get total population per grid cell clip_grid['tot_pop'] = clip_grid.geometry.apply(lambda x: zonal_stats(x,world_pop,stats="sum")) clip_grid['tot_pop'] = clip_grid['tot_pop'].apply(lambda x: x[0]['sum']) # remove cells in the grid that have no population data clip_grid = clip_grid.loc[~pd.isna(clip_grid.tot_pop)] clip_grid = clip_grid.loc[clip_grid.tot_pop > 100] clip_grid.reset_index(inplace=True,drop=True) clip_grid.geometry = clip_grid.geometry.centroid clip_grid['GID_0'] = GID_0 clip_grid['grid_height'] = height return clip_grid
def squareness(collection): """ Measures how different is a given shape from an equi-areal square The index is close to 0 for highly irregular shapes and to 1.3 for circular shapes. It equals 1 for squares. .. math:: \\begin{equation} \\frac{ \\sqrt{A}}{P^{2}} \\times \\frac{\\left(4 \\sqrt{\\left.A\\right)}^{2}\\right.}{\\sqrt{A}} = \\frac{\\left(4 \\sqrt{A}\\right)^{2}}{P{ }^{2}} = \\left(\\frac{4 \\sqrt{A}}{P}\\right)^{2} \\end{equation} where :math:`A` is the area and :math:`P` is the perimeter. Notes ----- Implementation follows :cite:`basaraner2017`. """ ga = _cast(collection) return ((numpy.sqrt(pygeos.area(ga)) * 4) / pygeos.length(ga))**2
def compute_similarity_matrix(self, detections, ground_truths, label_mean_area=None): r"""Compute the iou scores between all pairs of geometries with an Rtree on detections to speed up computation. Args: detections (ndarray, list) : A ndarray of detections stored as: * Bounding boxes for a given class where each row is a detection stored as: ``[BoundingBox, confidence]`` * Polygons for a given class where each row is a detection stored as: ``[Polygon, confidence]`` ground_truths (ndarray,list) : A ndarray of ground truth stored as: * Bounding boxes for a given class where each row is a ground truth stored as: ``[BoundingBox]`` * Polygons for a given class where each row is a ground truth stored as: ``[Polygon]`` label_mean_area (float) : Optional, default to ``None``. The mean area for each label in the dataset, if given, it is used to match with *iIoU* instead of *IoU* (c.f. :ref:`iiou`) Returns: ndarray : An IoU matrix (#detections, #ground truth) """ detections = self._sort_detection_by_confidence(detections) iou = intersection_over_union(detections[:, 0], ground_truths[:, 0]) if label_mean_area is not None: iou = (label_mean_area / area(ground_truths[:, 0])) * iou return iou
def get_parca(self): parca = gp.read_feather(parca_filename) df = intersection(pd.DataFrame({"geometry": self.geometry}), parca) if not len(df): return None df["acres"] = pg.area(df.geometry_right.values.data) * M2_ACRES df = df.loc[df.acres > 0].copy() # aggregate these back up by ID by_parca = (df[[ "parca_id", "name", "description", "acres" ]].groupby(by=[df.index.get_level_values(0), "parca_id"]).agg({ "name": "first", "description": "first", "acres": "sum" }).reset_index().rename(columns={"level_0": "id"})) by_parca.acres = by_parca.acres.astype("float32").round() return { "parca": by_parca[["name", "description", "acres"]].to_dict(orient="records") }
def moment_of_inertia(collection): """ Computes the moment of inertia of the polygon. This treats each boundary point as a point-mass of 1. Thus, for constant unit mass at each boundary point, the MoI of this pointcloud is \sum_i d_{i,c}^2 where c is the centroid of the polygon Altman's OS_1 measure, cited in Boyce and Clark (1964), also used in Weaver and Hess (1963). """ ga = _cast(collection) coords = pygeos.get_coordinates(ga) geom_ixs = numpy.repeat(numpy.arange(len(ga)), pygeos.get_num_coordinates(ga)) centroids = pygeos.get_coordinates(pygeos.centroid(ga))[geom_ixs] squared_euclidean = numpy.sum((coords - centroids)**2, axis=1) dists = (pandas.DataFrame.from_dict( dict(d2=squared_euclidean, geom_ix=geom_ixs)).groupby("geom_ix").d2.sum()).values return pygeos.area(ga) / numpy.sqrt(2 * dists)
def country_grid_gdp_filled(trans_network, country, data_path, rough_grid_split=100, from_main_graph=False): """[summary] Args: trans_network ([type]): [description] rough_grid_split (int, optional): [description]. Defaults to 100. Returns: [type]: [description] """ if from_main_graph == True: node_df = trans_network.copy() envelop = pygeos.envelope( pygeos.multilinestrings(node_df.geometry.values)) height = np.sqrt(pygeos.area(envelop) / rough_grid_split) else: node_df = trans_network.nodes.copy() node_df.geometry, approximate_crs = convert_crs(node_df) envelop = pygeos.envelope( pygeos.multilinestrings(node_df.geometry.values)) height = np.sqrt(pygeos.area(envelop) / rough_grid_split) gdf_admin = pd.DataFrame(create_grid(create_bbox(node_df), height), columns=['geometry']) #load data and convert to pygeos country_shape = gpd.read_file(os.path.join(data_path, 'GADM', 'gadm36_levels.gpkg'), layer=0) country_shape = pd.DataFrame( country_shape.loc[country_shape.GID_0 == country]) country_shape.geometry = pygeos.from_shapely(country_shape.geometry) gdf_admin = pygeos.intersection(gdf_admin, country_shape.geometry) gdf_admin = gdf_admin.loc[~pygeos.is_empty(gdf_admin.geometry)] gdf_admin['centroid'] = pygeos.centroid(gdf_admin.geometry) gdf_admin['km2'] = area(gdf_admin) gdf_admin['gdp'] = get_gdp_values(gdf_admin, data_path) gdf_admin = gdf_admin.loc[gdf_admin.gdp > 0].reset_index() gdf_admin['gdp_area'] = gdf_admin.gdp / gdf_admin['km2'] return gdf_admin
def test_rtree_iou_matrix(self): matcher = MatchEngineIoU(0.1, 'coco') ref_iou = naive_compute_iou_matrix(sort_detection_by_confidence(detections), gt) * \ (gt_mean_area / area(gt[:, 0])) iou = matcher.compute_similarity_matrix(detections, gt, label_mean_area=gt_mean_area) print(iou) print(ref_iou) assert np.all(iou[np.logical_not(np.isinf(iou))] == ref_iou[np.logical_not(np.isinf(iou))])
def nmi(collection): """ Computes the Normalized Moment of Inertia from Li et al (2013), recognizing that it is the relationship between the area of a shape squared divided by its second moment of area. """ ga = _cast(collection) return pygeos.area(ga)**2 / (2 * second_areal_moment(ga) * numpy.pi)
def rectangularity(collection): """ Ratio of the area of the shape to the area of its minimum bounding rotated rectangle Reveals a polygon’s degree of being curved inward. .. math:: \\frac{A}{A_{MBR}} where :math:`A` is the area and :math:`A_{MBR}` is the area of minimum bounding rotated rectangle. Notes ----- Implementation follows :cite:`basaraner2017`. """ ga = _cast(collection) return pygeos.area(ga) / pygeos.area(pygeos.minimum_rotated_rectangle(ga))
def overlay_entropy(a, b, standardize=True, local=False, base=numpy.e): """ The entropy of how n zones in a are split by m partitions in b, where n is the number of polygons in a and m is the number of partitions in b. This is the "overlay entropy", since the set of polygons constructed from intersection(a,b) is often called the "overlay" of A onto B. Larger when zones in a are uniformly split into many even pieces by partitions in b, and small when zones in A correspond well to zones in B. Arguments ---------- a : geometry array of polygons a set of polygons (the "target") for whom the areal entropy is calculated b : geometry array of polygons a set of polygons (the "frame") that splits a Returns -------- (n,) array expressing the entropy of the areal distributions of a's splits by partition b. Example ------- >>> r1 = geopandas.read_file('tests/regions.zip', layer='regions1') >>> r2 = geopandas.read_file('tests/regions.zip', layer='regions2') >>> overlay_entropy(r1, r2) """ a = _cast(a) b = _cast(b) aix, bix, ab = _overlay(a, b, return_indices=True) a_areas = pygeos.area(a) b_areas = pygeos.area(b) h = _overlay_entropy(aix, a_areas, pygeos.area(ab), base=base) if standardize: h /= areal_entropy(None, areas=b_areas, local=False, base=base) if local: return h return h.sum()
def test_polygon_with_none_hole(): actual = pygeos.polygons( pygeos.linearrings(box_tpl(0, 0, 10, 10)), [ pygeos.linearrings(box_tpl(1, 1, 2, 2)), None, pygeos.linearrings(box_tpl(3, 3, 4, 4)), ], ) assert pygeos.area(actual) == 98.0
def isoareal_quotient(collection): """ The Isoareal quotient, defined as the ratio of a polygon's perimeter to the perimeter of the equi-areal circle Altman's PA_3 measure, and proportional to the PA_4 measure """ ga = _cast(collection) return (2 * numpy.pi * numpy.sqrt( pygeos.area(ga) / numpy.pi)) / pygeos.measurement.length(ga)
def radii_ratio(collection): """ The Flaherty & Crumplin (1992) index, OS_3 in Altman (1998). The ratio of the radius of the equi-areal circle to the radius of the MBC """ ga = _cast(collection) r_eac = numpy.sqrt(pygeos.area(ga) / numpy.pi) r_mbc = pygeos.minimum_bounding_radius(ga) return r_eac / r_mbc
def equivalent_rectangular_index(collection): """ Deviation of a polygon from an equivalent rectangle .. math:: \\frac{\\sqrt{A}}{A_{MBR}} \\times \\frac{P_{MBR}}{P} where :math:`A` is the area, :math:`A_{MBR}` is the area of minimum bounding rotated rectangle, :math:`P` is the perimeter, :math:`P_{MBR}` is the perimeter of minimum bounding rotated rectangle. Notes ----- Implementation follows :cite:`basaraner2017`. """ ga = _cast(collection) box = pygeos.minimum_rotated_rectangle(ga) return numpy.sqrt(pygeos.area(ga) / pygeos.area(box)) * ( pygeos.length(box) / pygeos.length(ga))
def __init__(self, gdf, areas=None): self.gdf = gdf gdf = gdf.copy() if areas is None: areas = gdf.geometry.area if not isinstance(areas, str): gdf["mm_a"] = areas areas = "mm_a" self.areas = gdf[areas] exts = pygeos.area(pygeos.polygons(gdf.geometry.exterior.values.data)) self.series = pd.Series(exts - gdf[areas], index=gdf.index)
def get_ownership(self): ownership = gp.read_feather(ownership_filename) df = intersection(pd.DataFrame({"geometry": self.geometry}), ownership) if not len(df): return None df["acres"] = pg.area(df.geometry_right.values.data) * M2_ACRES df = df.loc[df.acres > 0].copy() if not len(df): return None results = dict() by_owner = (df[[ "FEE_ORGTYP", "acres" ]].groupby(by="FEE_ORGTYP").acres.sum().astype("float32").to_dict()) # use the native order of OWNERSHIP to drive order of results results["ownership"] = [{ "label": value["label"], "acres": by_owner[key] } for key, value in OWNERSHIP.items() if key in by_owner] by_protection = (df[[ "GAP_STATUS", "acres" ]].groupby(by="GAP_STATUS").acres.sum().astype("float32").to_dict()) # use the native order of PROTECTION to drive order of results results["protection"] = [{ "label": value["label"], "acres": by_protection[key] } for key, value in PROTECTION.items() if key in by_protection] by_area = (df[["AREA_NAME", "FEE_OWNER", "acres"]].groupby( by=[df.index.get_level_values(0), "AREA_NAME", "FEE_OWNER" ]).acres.sum().astype("float32").round().reset_index().rename( columns={ "level_0": "id", "AREA_NAME": "name", "FEE_OWNER": "owner" }).sort_values(by="acres", ascending=False)) # drop very small areas, these are not helpful by_area = by_area.loc[by_area.acres >= 1].copy() results["protected_areas"] = by_area.head(25).to_dict(orient="records") results["num_protected_areas"] = len(by_area) return results
def summarize_by_aoi(df, analysis_acres, total_acres): """Calculate ranks and areas of overlap within Caribbean Priority Watersheds. Parameters ---------- df : GeoDataframe area of interest analysis_acres : float area in acres of area of interest less any area outside SE Blueprint total_acres : float area in acres of area of interest dict { "priorities": [...], "legend": [...], "analysis_notes": <analysis_notes> } """ car_df = gp.read_feather(caribbean_filename, columns=["geometry", "carrank"]) df = intersection(df, car_df) df["acres"] = pg.area(df.geometry_right.values.data) * M2_ACRES # aggregate totals by rank by_rank = (df[["carrank", "acres"]].groupby( by="carrank").acres.sum().astype("float32").reset_index().sort_values( by="carrank")) priorities = [] for ix, row in by_rank.iterrows(): value = get_rank_value(row.carrank) value["acres"] = row.acres value["percent"] = 100 * row.acres / analysis_acres priorities.append(value) # Note: input area remainder deliberately omitted, since all # areas outside but close to this input are outside SE Blueprint return { "priorities": priorities, "legend": LEGEND, "analysis_notes": get_analysis_notes(), "analysis_acres": analysis_acres, "total_acres": total_acres, }
def areal_entropy(polygons=None, areas=None, local=False, base=numpy.e): """ Compute the entropy of the distribution of polygon areas. Arguments --------- polygons: numpy array of geometries polygons whose distribution of entropies needs to be computed. Should not be provided if areas is provided. areas: numpy array areas to use to compute entropy. SHould not be provided if polygons are provided. local: bool (default: False) whether to return the total entropy of the areal distribution (False), or to return the contribution to entropy made by each of area (True). Returns ------- Total map entropy or (n,) vector of local entropies. Example ------- >>> r1 = geopandas.read_file('tests/regions.zip', layer='regions1') >>> r2 = geopandas.read_file('tests/regions.zip', layer='regions2') >>> areal_entropy(polygons=r1) """ assert not ( (polygons is None) & (areas is None) ), "either polygons or precomputed areas must be provided" assert not ( (polygons is not None) & (areas is not None) ), "only one of polygons or areas should be provided." if polygons is None: assert areas is not None, "If polygons are not provided, areas should be." if areas is None: assert polygons is not None, "If areas are not provided, polygons should be." polygons = _cast(polygons) areas = pygeos.area(polygons) result = entr(areas / areas.sum()) / numpy.log(base) result[result < 0] = 0 if local: return result return result.sum()
def shape_index(collection): """ Schumm’s shape index (Schumm (1956) in MacEachren 1985) .. math:: {\\sqrt{{A} \\over {\\pi}}} \\over {R} where :math:`A` is the area and :math:`R` is the radius of the minimum bounding circle. Notes ----- Implementation follows :cite:`maceachren1985compactness`. """ ga = _cast(collection) return numpy.sqrt( pygeos.area(ga) / numpy.pi) / pygeos.minimum_bounding_radius(ga)
def completeness(a, b, local=False, base=numpy.e): """ The completeness of the partitions of polygons in a to those in a. Closer to 1 when all polygons in a are fully contained within polygons in b. From :cite:`nowosad2018` Arguments --------- a : geometry array of polygons array of polygons b : geometry array of polygons array of polygons local: bool (default: False) whether or not to provide local scores for each polygon. If True, the completeness for polygons in a are returned. scale: bool (default: None) whether to scale the completeness score(s). By default, completeness is is scaled for local scores so that the average of the local scores is the overall map completeness. If not local, then completeness is returned unscaled. You can also set local=True and scale=False to get raw components of the completeness, whose sum is the completeness for the entire map. Global re-scaled scores (local=False & scale=True) are not supported. base: bool (default=None) what base to use for the entropy calculations. The default is base e, which means entropy is measured in "nats." Example ------- >>> r1 = geopandas.read_file('tests/regions.zip', layer='regions1') >>> r2 = geopandas.read_file('tests/regions.zip', layer='regions2') >>> completeness(r1, r2) """ a = _cast(a) b = _cast(b) ohi = overlay_entropy(a, b, standardize=True, local=True, base=base) a_areas = pygeos.area(a) w = a_areas / a_areas.sum() ci = (w * (1 - ohi)) / w.sum() if local: return ci return ci.sum()