Skip to content

Commit

Permalink
ENH: either support MultiIndex or raise an error when one is given (#622
Browse files Browse the repository at this point in the history
)

* multiindex tests

* multiindex additions

* fixed nearest street test

* mypy

* changes

* Apply suggestions from code review

Co-authored-by: Martin Fleischmann <martin@martinfleischmann.net>

* docstring changes

---------

Co-authored-by: Martin Fleischmann <martin@martinfleischmann.net>
  • Loading branch information
u3ks and martinfleis authored Jun 20, 2024
1 parent 555c51e commit 9a64a1a
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 23 deletions.
68 changes: 48 additions & 20 deletions momepy/functional/_elements.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from libpysal.cg import voronoi_frames
from libpysal.graph import Graph
from packaging.version import Version
from pandas import Series
from pandas import MultiIndex, Series

GPD_GE_013 = Version(gpd.__version__) >= Version("0.13.0")
GPD_GE_10 = Version(gpd.__version__) >= Version("1.0dev")
Expand Down Expand Up @@ -103,6 +103,12 @@ def morphological_tessellation(
4 POLYGON ((1603084.231 6464104.386, 1603083.773...
"""

if isinstance(geometry.index, MultiIndex):
raise ValueError(
"MultiIndex is not supported in `momepy.morphological_tessellation`."
)

if isinstance(clip, GeoSeries | GeoDataFrame):
clip = clip.union_all() if GPD_GE_10 else clip.unary_union

Expand Down Expand Up @@ -218,6 +224,11 @@ def enclosed_tessellation(
126 POLYGON ((1603528.593 6464221.033, 1603527.796... 0
"""

if isinstance(geometry.index, MultiIndex):
raise ValueError(
"MultiIndex is not supported in `momepy.enclosed_tessellation`."
)

# convert to GeoDataFrame and add position (we will need it later)
enclosures = enclosures.geometry.to_frame()
enclosures["position"] = range(len(enclosures))
Expand Down Expand Up @@ -360,6 +371,12 @@ def verify_tessellation(tessellation, geometry):
>>> excluded, multipolygons = momepy.verify_tessellation(tessellation, buildings)
"""

if isinstance(geometry.index, MultiIndex) or isinstance(
tessellation.index, MultiIndex
):
raise ValueError("MultiIndex is not supported in `momepy.verify_tessellation`.")

# check against input layer
ids_original = geometry.index
ids_generated = tessellation.index
Expand Down Expand Up @@ -397,7 +414,7 @@ def get_nearest_street(
buildings: GeoSeries | GeoDataFrame,
streets: GeoSeries | GeoDataFrame,
max_distance: float | None = None,
) -> np.ndarray:
) -> Series:
"""Identify the nearest street for each building.
Parameters
Expand Down Expand Up @@ -429,30 +446,26 @@ def get_nearest_street(
Get street index.
>>> momepy.get_nearest_street(buildings, streets)
array([ 0., 33., 10., 8., 8., 8., 8., 8., 33., 11., 11., 28., 28.,
28., 28., 28., 16., 8., 8., 8., 8., 8., 8., 11., 28., 28.,
28., 8., 8., 8., 8., 16., 28., 28., 28., 28., 28., 1., 21.,
21., 21., 21., 21., 12., 12., 12., 26., 26., 26., 19., 19., 19.,
19., 21., 21., 21., 32., 32., 32., 32., 32., 26., 26., 5., 5.,
5., 5., 2., 2., 2., 2., 2., 2., 25., 25., 25., 19., 19.,
19., 19., 5., 25., 6., 33., 33., 33., 33., 33., 33., 33., 34.,
34., 34., 34., 34., 34., 34., 34., 6., 6., 6., 6., 6., 34.,
33., 6., 34., 34., 34., 34., 0., 0., 0., 0., 0., 0., 34.,
34., 34., 0., 0., 14., 2., 2., 25., 24., 2., 2., 2., 2.,
24., 24., 24., 24., 24., 28., 12., 28., 34., 34., 32., 21., 16.,
19.], dtype=float32)
0 0.0
1 33.0
2 10.0
3 8.0
4 8.0
...
139 34.0
140 32.0
141 21.0
142 16.0
143 19.0
Length: 144, dtype: float64
"""
blg_idx, str_idx = streets.sindex.nearest(
buildings.geometry, return_all=False, max_distance=max_distance
)

if streets.index.dtype == "object":
ids = np.empty(len(buildings), dtype=object)
else:
ids = np.empty(len(buildings), dtype=np.float32)
ids[:] = np.nan
ids = pd.Series(None, index=buildings.index, dtype=streets.index.dtype)

ids[blg_idx] = streets.index[str_idx]
ids.iloc[blg_idx] = streets.index[str_idx]
return ids


Expand Down Expand Up @@ -522,6 +535,15 @@ def get_nearest_node(
143 22.0
Length: 144, dtype: float64
"""

if (
isinstance(buildings.index, MultiIndex)
or isinstance(nearest_edge.index, MultiIndex)
or isinstance(nodes.index, MultiIndex)
or isinstance(edges.index, MultiIndex)
):
raise ValueError("MultiIndex is not supported in `momepy.get_nearest_node`.")

# treat possibly missing edge index
a = np.empty(len(buildings))
na_mask = np.isnan(nearest_edge)
Expand Down Expand Up @@ -606,6 +628,12 @@ def generate_blocks(
>>> tessellation["block_id"] = tessellation_id
"""

if (
isinstance(buildings.index, MultiIndex)
or isinstance(tessellation.index, MultiIndex)
or isinstance(edges.index, MultiIndex)
):
raise ValueError("MultiIndex is not supported in `momepy.generate_blocks`.")
id_name: str = "bID"

# slice the tessellations by the street network
Expand Down
8 changes: 6 additions & 2 deletions momepy/functional/_shape.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from geopandas import GeoDataFrame, GeoSeries
from numpy.typing import NDArray
from packaging.version import Version
from pandas import DataFrame, Series
from pandas import DataFrame, MultiIndex, Series

from momepy.functional import _dimension

Expand Down Expand Up @@ -724,6 +724,10 @@ def centroid_corner_distance(
"momepy.centroid_corner_distance requires geopandas 0.13 or later. "
)

result_index = geometry.index
if isinstance(geometry.index, MultiIndex):
geometry = geometry.reset_index(drop=True)

def _ccd(points: DataFrame, eps: float) -> Series:
centroid = points.values[0, 2:]
pts = points.values[:-1, :2]
Expand All @@ -738,7 +742,7 @@ def _ccd(points: DataFrame, eps: float) -> Series:
coords = geometry.exterior.get_coordinates(index_parts=False)
coords[["cent_x", "cent_y"]] = geometry.centroid.get_coordinates(index_parts=False)
ccd = coords.groupby(level=0).apply(_ccd, eps=eps)
ccd.index = geometry.index
ccd.index = result_index
return ccd


Expand Down
33 changes: 32 additions & 1 deletion momepy/functional/tests/test_elements.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ def test_get_nearest_street(self):

streets.index = streets.index.astype(str)
nearest = mm.get_nearest_street(self.df_buildings, streets, 10)
assert (nearest == None).sum() == 137 # noqa: E711
assert pd.isna(nearest).sum() == 137 # noqa: E711

def test_get_nearest_node(self):
nodes, edges = mm.nx_to_gdf(mm.gdf_to_nx(self.df_streets))
Expand Down Expand Up @@ -332,6 +332,37 @@ def test_blocks_inner(self):
else:
assert len(blocks.sindex.query_bulk(blocks.geometry, "overlaps")[0]) == 0

def test_multi_index(self):
buildings = self.df_buildings.set_index(["uID", "uID"])
with pytest.raises(
ValueError,
match="MultiIndex is not supported in `momepy.morphological_tessellation`.",
):
mm.morphological_tessellation(buildings)
with pytest.raises(
ValueError,
match="MultiIndex is not supported in `momepy.enclosed_tessellation`.",
):
mm.enclosed_tessellation(buildings, self.enclosures)
with pytest.raises(
ValueError,
match="MultiIndex is not supported in `momepy.verify_tessellation`.",
):
mm.verify_tessellation(buildings, self.enclosures)

with pytest.raises(
ValueError,
match="MultiIndex is not supported in `momepy.get_nearest_node`.",
):
mm.get_nearest_node(
buildings, self.enclosures, self.enclosures, self.enclosures
)

with pytest.raises(
ValueError, match="MultiIndex is not supported in `momepy.generate_blocks`"
):
mm.generate_blocks(buildings, self.enclosures, self.enclosures)

def test_tess_single_building_edge_case(self):
tessellations = mm.enclosed_tessellation(
self.df_buildings, self.enclosures.geometry, n_jobs=-1
Expand Down

0 comments on commit 9a64a1a

Please sign in to comment.