-
Notifications
You must be signed in to change notification settings - Fork 58
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
functional courtyards calculation (#572)
* functional courtyards calculation * Apply suggestions from code review Co-authored-by: Martin Fleischmann <martin@martinfleischmann.net> * test change --------- Co-authored-by: Martin Fleischmann <martin@martinfleischmann.net>
- Loading branch information
1 parent
4486126
commit 8e93b6c
Showing
3 changed files
with
123 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import shapely | ||
from geopandas import GeoDataFrame, GeoSeries | ||
from libpysal.graph import Graph | ||
from pandas import Series | ||
|
||
__all__ = ["courtyards"] | ||
|
||
|
||
def courtyards(geometry: GeoDataFrame | GeoSeries, graph: Graph) -> Series: | ||
"""Calculate the number of courtyards within the joined structure. | ||
Adapted from :cite:`schirmer2015`. | ||
Parameters | ||
---------- | ||
geometry : GeoDataFrame | ||
A GeoDataFrame containing objects to analyse. | ||
graph : libpysal.graph.Graph | ||
A spatial weights matrix for the geodataframe, | ||
it is used to denote adjacent buildings. | ||
Returns | ||
------- | ||
Series | ||
A Series containing the resulting values. | ||
Examples | ||
-------- | ||
>>> courtyards = mm.calculate_courtyards(buildings_df, graph) | ||
""" | ||
|
||
def _calculate_courtyards(group): | ||
"""helper function to carry out the per group calculations""" | ||
return shapely.get_num_interior_rings( | ||
shapely.union_all(shapely.buffer(group.values, 0.01)) | ||
) | ||
|
||
# calculate per group courtyards | ||
temp_result = ( | ||
geometry["geometry"] | ||
.groupby(graph.component_labels.values) | ||
.apply(_calculate_courtyards) | ||
) | ||
# assign group courtyard values to each building in the group | ||
## note: this is faster than using transform directly | ||
result = Series( | ||
temp_result.loc[graph.component_labels.values].values, index=geometry.index | ||
) | ||
|
||
return result |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
import geopandas as gpd | ||
import numpy as np | ||
from libpysal.graph import Graph | ||
from pandas.testing import assert_series_equal | ||
|
||
import momepy as mm | ||
|
||
from .conftest import assert_result | ||
|
||
|
||
class TestIntensity: | ||
def setup_method(self): | ||
test_file_path = mm.datasets.get_path("bubenec") | ||
self.df_buildings = gpd.read_file(test_file_path, layer="buildings") | ||
self.df_streets = gpd.read_file(test_file_path, layer="streets") | ||
self.df_tessellation = gpd.read_file(test_file_path, layer="tessellation") | ||
self.df_streets["nID"] = mm.unique_id(self.df_streets) | ||
self.df_buildings["height"] = np.linspace(10.0, 30.0, 144) | ||
self.df_tessellation["area"] = self.df_tessellation.geometry.area | ||
self.df_buildings["area"] = self.df_buildings.geometry.area | ||
self.df_buildings["fl_area"] = mm.FloorArea(self.df_buildings, "height").series | ||
self.df_buildings["nID"] = mm.get_network_id( | ||
self.df_buildings, self.df_streets, "nID" | ||
) | ||
blocks = mm.Blocks( | ||
self.df_tessellation, self.df_streets, self.df_buildings, "bID", "uID" | ||
) | ||
self.blocks = blocks.blocks | ||
self.df_buildings["bID"] = blocks.buildings_id | ||
self.df_tessellation["bID"] = blocks.tessellation_id | ||
|
||
self.buildings_graph = Graph.build_contiguity( | ||
self.df_buildings, rook=False | ||
).assign_self_weight() | ||
|
||
def test_courtyards(self): | ||
courtyards = mm.courtyards(self.df_buildings, self.buildings_graph) | ||
expected = {"mean": 0.6805555555555556, "sum": 98, "min": 0, "max": 1} | ||
assert_result(courtyards, expected, self.df_buildings) | ||
|
||
|
||
class TestIntensityEquality: | ||
def setup_method(self): | ||
test_file_path = mm.datasets.get_path("bubenec") | ||
self.df_buildings = gpd.read_file(test_file_path, layer="buildings") | ||
self.df_streets = gpd.read_file(test_file_path, layer="streets") | ||
self.df_tessellation = gpd.read_file(test_file_path, layer="tessellation") | ||
self.df_streets["nID"] = mm.unique_id(self.df_streets) | ||
self.df_buildings["height"] = np.linspace(10.0, 30.0, 144) | ||
self.df_tessellation["area"] = self.df_tessellation.geometry.area | ||
self.df_buildings["area"] = self.df_buildings.geometry.area | ||
self.df_buildings["fl_area"] = mm.FloorArea(self.df_buildings, "height").series | ||
self.df_buildings["nID"] = mm.get_network_id( | ||
self.df_buildings, self.df_streets, "nID" | ||
) | ||
blocks = mm.Blocks( | ||
self.df_tessellation, self.df_streets, self.df_buildings, "bID", "uID" | ||
) | ||
self.blocks = blocks.blocks | ||
self.df_buildings["bID"] = blocks.buildings_id | ||
self.df_tessellation["bID"] = blocks.tessellation_id | ||
|
||
self.buildings_graph = Graph.build_contiguity( | ||
self.df_buildings, rook=False | ||
).assign_self_weight() | ||
|
||
def test_courtyards(self): | ||
old_courtyards = mm.Courtyards(self.df_buildings).series | ||
new_courtyards = mm.courtyards(self.df_buildings, self.buildings_graph) | ||
assert_series_equal( | ||
new_courtyards, old_courtyards, check_names=False, check_dtype=False | ||
) |