Skip to content

Commit

Permalink
functional courtyards calculation (#572)
Browse files Browse the repository at this point in the history
* 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
u3ks and martinfleis committed Apr 25, 2024
1 parent 4486126 commit 8e93b6c
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 0 deletions.
1 change: 1 addition & 0 deletions momepy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from .functional._distribution import *
from .functional._diversity import *
from .functional._elements import *
from .functional._intensity import *
from .functional._shape import *
from .graph import *
from .intensity import *
Expand Down
50 changes: 50 additions & 0 deletions momepy/functional/_intensity.py
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
72 changes: 72 additions & 0 deletions momepy/functional/tests/test_intensity.py
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
)

0 comments on commit 8e93b6c

Please sign in to comment.