diff --git a/docs/api.rst b/docs/api.rst index e571da60..1b9b3e96 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -77,6 +77,7 @@ spatial distribution NeighboringStreetOrientationDeviation Neighbors Orientation + SharedWalls SharedWallsRatio StreetAlignment diff --git a/docs/bibtex.json b/docs/bibtex.json index d41c8624..c539e875 100644 --- a/docs/bibtex.json +++ b/docs/bibtex.json @@ -68,6 +68,9 @@ "generated/momepy.Rectangularity": [ "dibble2017" ], + "generated/momepy.SharedWalls": [ + "hamaina2012a" + ], "generated/momepy.SharedWallsRatio": [ "hamaina2012a" ], diff --git a/momepy/distribution.py b/momepy/distribution.py index 31fbecdc..aebe183b 100644 --- a/momepy/distribution.py +++ b/momepy/distribution.py @@ -5,7 +5,6 @@ # definitions of spatial distribution characters import math -import warnings import networkx as nx import numpy as np @@ -16,6 +15,7 @@ __all__ = [ "Orientation", + "SharedWalls", "SharedWallsRatio", "StreetAlignment", "CellAlignment", @@ -106,7 +106,55 @@ def _dist(a, b): self.series = pd.Series(results_list, index=gdf.index) -class SharedWallsRatio: +class SharedWalls: + """ + Calculate the length of shared walls of adjacent elements (typically buildings) + + .. math:: + \\textit{length of shared walls} + + Note that data needs to be topologically correct. Overlapping polygons will lead to + incorrect results. + + Adapted from :cite:`hamaina2012a`. + + Parameters + ---------- + gdf : GeoDataFrame + GeoDataFrame containing gdf to analyse + + Attributes + ---------- + series : Series + Series containing resulting values + gdf : GeoDataFrame + original GeoDataFrame + + Examples + -------- + >>> buildings_df['swr'] = momepy.SharedWalls(buildings_df).series + + See also + -------- + SharedWallsRatio + """ + + def __init__(self, gdf): + self.gdf = gdf + + inp, res = gdf.sindex.query_bulk(gdf.geometry, predicate="intersects") + left = gdf.geometry.take(inp).reset_index(drop=True) + right = gdf.geometry.take(res).reset_index(drop=True) + intersections = left.intersection(right).length + results = intersections.groupby(inp).sum().reset_index( + drop=True + ) - gdf.geometry.length.reset_index(drop=True) + results.index = gdf.index + + self.series = results + + +class SharedWallsRatio(SharedWalls): """ Calculate shared walls ratio of adjacent elements (typically buildings) @@ -122,8 +170,7 @@ class SharedWallsRatio: ---------- gdf : GeoDataFrame GeoDataFrame containing gdf to analyse - unique_id : (deprecated) - perimeters : str, list, np.array, pd.Series (default None) + perimeters : str, list, np.array, pd.Series (default None, optional) the name of the dataframe column, ``np.array``, or ``pd.Series`` where is stored perimeter value Attributes @@ -140,15 +187,14 @@ class SharedWallsRatio: >>> buildings_df['swr'] = momepy.SharedWallsRatio(buildings_df).series >>> buildings_df['swr'][10] 0.3424804411228673 - """ - def __init__(self, gdf, unique_id=None, perimeters=None): - if unique_id is not None: - warnings.warn( - "unique_id is deprecated and will be removed in v0.4.", FutureWarning, - ) + See also + -------- + SharedWalls + """ - self.gdf = gdf + def __init__(self, gdf, perimeters=None): + super(SharedWallsRatio, self).__init__(gdf) if perimeters is None: self.perimeters = gdf.geometry.length @@ -157,17 +203,7 @@ def __init__(self, gdf, unique_id=None, perimeters=None): else: self.perimeters = perimeters - inp, res = gdf.sindex.query_bulk(gdf.geometry, predicate="intersects") - left = gdf.geometry.take(inp).reset_index(drop=True) - right = gdf.geometry.take(res).reset_index(drop=True) - intersections = left.intersection(right).length - results = ( - intersections.groupby(inp).sum().reset_index(drop=True) - - self.perimeters.reset_index(drop=True) - ) / self.perimeters.reset_index(drop=True) - results.index = gdf.index - - self.series = results + self.series = self.series / self.perimeters class StreetAlignment: diff --git a/tests/test_distribution.py b/tests/test_distribution.py index 0c4b9b88..8ebe3941 100644 --- a/tests/test_distribution.py +++ b/tests/test_distribution.py @@ -32,6 +32,15 @@ def test_Orientation(self): check = 40.7607 assert self.df_streets["orient"][0] == pytest.approx(check) + @pytest.mark.skipif(not GPD_08, reason="requires geopandas > 0.7") + def test_SharedWalls(self): + self.df_buildings["swr"] = mm.SharedWalls(self.df_buildings).series + nonconsecutive = self.df_buildings.drop(2) + result = mm.SharedWalls(nonconsecutive).series + check = 39.395484381507075 + assert self.df_buildings["swr"][10] == check + assert result[10] == check + @pytest.mark.skipif(not GPD_08, reason="requires geopandas > 0.7") def test_SharedWallsRatio(self): self.df_buildings["swr"] = mm.SharedWallsRatio(self.df_buildings).series @@ -44,8 +53,6 @@ def test_SharedWallsRatio(self): assert self.df_buildings["swr"][10] == check assert self.df_buildings["swr_array"][10] == check assert result[10] == check - with pytest.warns(FutureWarning): - mm.SharedWallsRatio(self.df_buildings, "uID") def test_StreetAlignment(self): self.df_buildings["orient"] = orient = mm.Orientation(self.df_buildings).series