Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Functional distribution #588

Merged
merged 11 commits into from
Jun 12, 2024
126 changes: 126 additions & 0 deletions momepy/functional/_distribution.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
from pandas import Series
from scipy import sparse

from momepy.utils import _azimuth

__all__ = [
"orientation",
"shared_walls",
Expand All @@ -16,6 +18,9 @@
"building_adjacency",
"neighbors",
"street_alignment",
"cell_alignment",
"shared_walls_ratio",
"neighboring_street_orientation_deviation",
]

GPD_GE_013 = Version(gpd.__version__) >= Version("0.13.0")
Expand Down Expand Up @@ -339,3 +344,124 @@ def street_alignment(
Series
"""
return (building_orientation - street_orientation.loc[street_index].values).abs()


def cell_alignment(
martinfleis marked this conversation as resolved.
Show resolved Hide resolved
left_orientations: np.ndarray | Series, right_orientations: np.ndarray | Series
):
martinfleis marked this conversation as resolved.
Show resolved Hide resolved
"""
Calculate the difference between cell orientation and the orientation of object.

.. math::
\\left|{\\textit{building orientation} - \\textit{cell orientation}}\\right|

Parameters
----------
left_orientations : np.array, pd.Series
The ``np.array``, or
`pd.Series`` where object orientation values are stored. This
u3ks marked this conversation as resolved.
Show resolved Hide resolved
can be calculated using :func:`orientation`.
right_orientations : np.array, pd.Series
The ``np.array`` or ``pd.Series``
where object orientation values are stored. This
u3ks marked this conversation as resolved.
Show resolved Hide resolved
can be calculated using :func:`orientation`.
martinfleis marked this conversation as resolved.
Show resolved Hide resolved

Returns
-------
Series
"""
return (left_orientations - right_orientations).abs()
martinfleis marked this conversation as resolved.
Show resolved Hide resolved


def shared_walls_ratio(
shared_walls: np.ndarray | Series, perimeters: np.ndarray | Series
):
"""Calculate shared walls ratio of adjacent elements (typically buildings).

.. math::
\\textit{length of shared walls} \\over perimeter

Note that data needs to be topologically correct.
Overlapping polygons will lead to incorrect results.

Adapted from :cite:`hamaina2012a`.

Parameters
----------
shared_walls : np.array, pd.Series
The ``np.array``, or
`pd.Series`` shared wall polygons are stored. This
can be calculated using :func:`shared_walls`.
perimeters : np.array, pd.Series
The ``np.array``, or
`pd.Series`` containing used perimeters values.
Typically the perimeters of the original GeoDataFrame
used to generate the shared walls.

Returns
-------
Series
"""

return shared_walls / perimeters
martinfleis marked this conversation as resolved.
Show resolved Hide resolved


def _orient(geom):
start = geom.coords[0]
end = geom.coords[-1]
az = _azimuth(start, end)
if 90 > az >= 45:
diff = az - 45
az = az - 2 * diff
elif 135 > az >= 90:
diff = az - 90
az = az - 2 * diff
diff = az - 45
az = az - 2 * diff
elif 181 > az >= 135:
diff = az - 135
az = az - 2 * diff
diff = az - 90
az = az - 2 * diff
diff = az - 45
az = az - 2 * diff
return az


def neighboring_street_orientation_deviation(gdf: gpd.GeoDataFrame) -> Series:
"""Calculate the mean deviation of solar orientation of adjacent streets.
The orientation of a street segment is represented by the orientation of the line
connecting the first and last point of the segment.

.. math::
\\frac{1}{n}\\sum_{i=1}^n dev_i=\\frac{dev_1+dev_2+\\cdots+dev_n}{n}

Parameters
----------
gdf : GeoDataFrame
A GeoDataFrame containing objects to analyse.

Returns
-------
Series
"""

orientation = gdf.geometry.apply(_orient)

if GPD_GE_013:
inp, res = gdf.sindex.query(gdf.geometry, predicate="intersects")
else:
inp, res = gdf.sindex.query_bulk(gdf.geometry, predicate="intersects")
itself = inp == res
inp = inp[~itself]
res = res[~itself]

left = orientation.take(inp).reset_index(drop=True)
right = orientation.take(res).reset_index(drop=True)
deviations = (left - right).abs()
stats = deviations.groupby(inp).mean()

results = Series(np.nan, gdf.index.values)
# iloc since sindex query returns a numpy iloc array
results.iloc[stats.index.values] = stats.values
return results
martinfleis marked this conversation as resolved.
Show resolved Hide resolved
59 changes: 59 additions & 0 deletions momepy/functional/tests/test_distribution.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,37 @@ def test_street_alignment(self):
r = mm.street_alignment(building_orientation, street_orientation, street_index)
assert_result(r, expected, self.df_buildings)

def test_cell_alignment(self):
df_buildings = self.df_buildings.reset_index()
df_tessellation = self.df_tessellation.reset_index()
df_buildings["orient"] = blgori = mm.Orientation(df_buildings).series
df_tessellation["orient"] = tessori = mm.Orientation(df_tessellation).series

align = mm.cell_alignment(blgori, tessori)
align_expected = {
"count": 144,
"mean": 2.60474658483889,
"max": 33.201625570390746,
"min": 7.857417408274614e-06,
}
assert_result(align, align_expected, df_buildings)

def test_shared_walls_ratio(self):
sw = mm.shared_walls(self.df_buildings)
swr = mm.shared_walls_ratio(sw, self.df_buildings.geometry.length)
swr_expected = {}
assert_result(swr, swr_expected, self.df_buildings)

def test_neighboring_street_orientation_deviation(self):
dev = mm.neighboring_street_orientation_deviation(self.df_streets)
dev_expected = {
"count": 35,
"mean": 7.527840590385933,
"max": 20.907684600229896,
"min": 0.007987047658392754,
}
assert_result(dev, dev_expected, self.df_streets)


class TestEquality:
def setup_method(self):
Expand Down Expand Up @@ -220,3 +251,31 @@ def test_street_alignment(self):
right_network_id="index",
).series
assert_series_equal(new, old, check_names=False, check_index=False)

def test_cell_alignment(self):
df_buildings = self.df_buildings.reset_index()
df_tessellation = self.df_tessellation.reset_index()
df_buildings["orient"] = blgori = mm.Orientation(df_buildings).series
df_tessellation["orient"] = tessori = mm.Orientation(df_tessellation).series

align_new = mm.cell_alignment(blgori, tessori)

align_old = mm.CellAlignment(
df_buildings, df_tessellation, "orient", "orient", "uID", "uID"
).series

assert_series_equal(align_new, align_old, check_names=False, check_dtype=False)

def test_shared_walls_ratio(self):
swr_old = mm.SharedWallsRatio(self.df_buildings).series

sw = mm.shared_walls(self.df_buildings)
swr_new = mm.shared_walls_ratio(sw, self.df_buildings.geometry.length)

assert_series_equal(swr_old, swr_new, check_names=False)

def test_neighboring_street_orientation_deviation(self):
dev_old = mm.NeighboringStreetOrientationDeviation(self.df_streets).series

dev_new = mm.neighboring_street_orientation_deviation(self.df_streets)
assert_series_equal(dev_old, dev_new, check_names=False)
Loading