Skip to content

Commit 81880e7

Browse files
authored
ENH: add simplify keyword to morphological_tessellation (#697)
* ENH: add simplify keyword to morphological_tessellation * update docstrings * adapt to old shapely * more adaptation
1 parent 8f41204 commit 81880e7

File tree

3 files changed

+65
-44
lines changed

3 files changed

+65
-44
lines changed

momepy/functional/_distribution.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -531,16 +531,16 @@ def neighbors(
531531
>>> momepy.neighbors(tessellation, contig, weighted=True)
532532
focal
533533
0 0.012732
534-
1 0.010116
535-
2 0.013350
536-
3 0.010172
537-
4 0.038916
538-
...
539-
139 0.020037
540-
140 0.036766
541-
141 0.045287
542-
142 0.044147
543-
143 0.051799
534+
1 0.010126
535+
2 0.013391
536+
3 0.010180
537+
4 0.038930
538+
...
539+
139 0.020039
540+
140 0.036771
541+
141 0.045290
542+
142 0.044329
543+
143 0.051833
544544
Name: neighbors, Length: 144, dtype: float64
545545
"""
546546
if weighted:
@@ -668,8 +668,8 @@ def cell_alignment(
668668
...
669669
139 0.189750
670670
140 17.920139
671-
141 0.393708
672-
142 0.024618
671+
141 0.394787
672+
142 0.014295
673673
143 0.252122
674674
Name: orientation, Length: 144, dtype: float64
675675
"""

momepy/functional/_elements.py

Lines changed: 36 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ def morphological_tessellation(
3030
clip: str | shapely.Geometry | GeoSeries | GeoDataFrame | None = "bounding_box",
3131
shrink: float = 0.4,
3232
segment: float = 0.5,
33+
simplify: bool = True,
3334
**kwargs,
3435
) -> GeoDataFrame:
3536
"""Generate morphological tessellation.
@@ -70,6 +71,9 @@ def morphological_tessellation(
7071
By default 0.4
7172
segment : float, optional
7273
The maximum distance between points after discretization. By default 0.5
74+
simplify: bool, optional
75+
Whether to attempt to simplify the resulting tesselation boundaries with
76+
``shapely.coverage_simplify``. By default True.
7377
**kwargs
7478
Additional keyword arguments pased to libpysal.cg.voronoi_frames, such as
7579
``grid_size``.
@@ -98,13 +102,20 @@ def morphological_tessellation(
98102
99103
>>> momepy.morphological_tessellation(buildings).head()
100104
geometry
101-
0 POLYGON ((1603577.153 6464348.291, 1603576.946...
102-
1 POLYGON ((1603166.356 6464326.62, 1603166.425 ...
103-
2 POLYGON ((1603006.941 6464167.63, 1603009.97 6...
104-
3 POLYGON ((1602995.269 6464132.007, 1603001.768...
105-
4 POLYGON ((1603084.231 6464104.386, 1603083.773...
105+
0 POLYGON ((1603536.56 6464392.264, 1603541.262 ...
106+
1 POLYGON ((1603167.679 6464323.194, 1603167.552...
107+
2 POLYGON ((1603078.787 6464172.1, 1603077.665 6...
108+
3 POLYGON ((1603070.306 6464154.611, 1603070.081...
109+
4 POLYGON ((1603083.134 6464103.971, 1603077.387...
106110
107111
"""
112+
if simplify and not SHPLY_GE_210:
113+
# TODO: remove the keyword and do simplification by default once it is
114+
# safe to pin shapely 2.1
115+
raise ImportError(
116+
"`simplify=True` requires shapely 2.1 or higher. "
117+
"Update shapely or set `simplify` to False."
118+
)
108119

109120
if isinstance(geometry.index, MultiIndex):
110121
raise ValueError(
@@ -114,7 +125,7 @@ def morphological_tessellation(
114125
if isinstance(clip, GeoSeries | GeoDataFrame):
115126
clip = clip.union_all() if GPD_GE_10 else clip.unary_union
116127

117-
return voronoi_frames(
128+
mt = voronoi_frames(
118129
geometry,
119130
clip=clip,
120131
shrink=shrink,
@@ -123,6 +134,11 @@ def morphological_tessellation(
123134
as_gdf=True,
124135
**kwargs,
125136
)
137+
if simplify:
138+
mt.geometry = shapely.coverage_simplify(
139+
mt.geometry, tolerance=segment / 2, simplify_boundary=False
140+
)
141+
return mt
126142

127143

128144
def enclosed_tessellation(
@@ -236,6 +252,8 @@ def enclosed_tessellation(
236252
"""
237253

238254
if simplify and not SHPLY_GE_210:
255+
# TODO: remove the keyword and do simplification by default once it is
256+
# safe to pin shapely 2.1
239257
raise ImportError(
240258
"`simplify=True` requires shapely 2.1 or higher. "
241259
"Update shapely or set `simplify` to False."
@@ -330,10 +348,9 @@ def _tess(ix, poly, blg, threshold, shrink, segment, enclosure_id, to_simplify,
330348
**kwargs,
331349
)
332350
if to_simplify:
333-
simpl_collection = shapely.coverage_simplify(
351+
tess.geometry = shapely.coverage_simplify(
334352
tess.geometry, tolerance=segment / 2, simplify_boundary=False
335353
)
336-
tess.geometry = gpd.GeoSeries(simpl_collection).values
337354
tess[enclosure_id] = ix
338355
return tess
339356

@@ -619,24 +636,24 @@ def generate_blocks(
619636
Generate tessellation:
620637
621638
>>> tessellation = momepy.morphological_tessellation(buildings)
622-
>>> tessellation
639+
>>> tessellation.head()
623640
geometry
624-
0 POLYGON ((1603577.153 6464348.291, 1603576.946...
625-
1 POLYGON ((1603166.356 6464326.62, 1603166.425 ...
626-
2 POLYGON ((1603006.941 6464167.63, 1603009.97 6...
627-
3 POLYGON ((1602995.269 6464132.007, 1603001.768...
628-
4 POLYGON ((1603084.231 6464104.386, 1603083.773...
641+
0 POLYGON ((1603536.56 6464392.264, 1603541.262 ...
642+
1 POLYGON ((1603167.679 6464323.194, 1603167.552...
643+
2 POLYGON ((1603078.787 6464172.1, 1603077.665 6...
644+
3 POLYGON ((1603070.306 6464154.611, 1603070.081...
645+
4 POLYGON ((1603083.134 6464103.971, 1603077.387...
629646
630647
>>> blocks, tessellation_id = momepy.generate_blocks(
631648
... tessellation, streets, buildings
632649
... )
633650
>>> blocks.head()
634651
geometry
635-
0 POLYGON ((1603500.079 6464214.019, 1603499.565...
636-
1 POLYGON ((1603431.893 6464278.302, 1603431.553...
637-
2 POLYGON ((1603321.257 6464125.859, 1603320.938...
638-
3 POLYGON ((1603137.411 6464124.658, 1603137.116...
639-
4 POLYGON ((1603179.384 6463961.584, 1603179.357...
652+
0 POLYGON ((1603421.741 6464282.377, 1603415.23 ...
653+
1 POLYGON ((1603485.548 6464217.177, 1603483.228...
654+
2 POLYGON ((1603314.034 6464117.593, 1603295.424...
655+
3 POLYGON ((1602992.334 6464131.13, 1602992.334 ...
656+
4 POLYGON ((1602992.334 6463992.499, 1602992.334...
640657
641658
``tessellation_id`` can be directly assigned to its
642659
respective parental DataFrame directly.

momepy/functional/tests/test_elements.py

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,14 @@ def setup_method(self):
2727
)
2828

2929
def test_morphological_tessellation(self):
30-
tessellation = mm.morphological_tessellation(
31-
self.df_buildings,
32-
)
30+
tessellation = mm.morphological_tessellation(self.df_buildings, simplify=False)
3331
assert (tessellation.geom_type == "Polygon").all()
3432
assert tessellation.crs == self.df_buildings.crs
3533
assert_index_equal(tessellation.index, self.df_buildings.index)
3634
assert isinstance(tessellation, gpd.GeoDataFrame)
3735

3836
clipped = mm.morphological_tessellation(
39-
self.df_buildings,
40-
clip=self.limit,
37+
self.df_buildings, clip=self.limit, simplify=False
4138
)
4239

4340
assert (tessellation.geom_type == "Polygon").all()
@@ -46,16 +43,15 @@ def test_morphological_tessellation(self):
4643
assert clipped.area.sum() < tessellation.area.sum()
4744

4845
sparser = mm.morphological_tessellation(
49-
self.df_buildings,
50-
segment=2,
46+
self.df_buildings, segment=2, simplify=False
5147
)
5248
assert (
5349
sparser.get_coordinates().shape[0] < tessellation.get_coordinates().shape[0]
5450
)
5551

5652
def test_morphological_tessellation_buffer_clip(self):
5753
tessellation = mm.morphological_tessellation(
58-
self.df_buildings, clip=self.df_buildings.buffer(50)
54+
self.df_buildings, clip=self.df_buildings.buffer(50), simplify=False
5955
)
6056
assert (tessellation.geom_type == "Polygon").all()
6157
assert tessellation.crs == self.df_buildings.crs
@@ -82,13 +78,21 @@ def test_morphological_tessellation_errors(self):
8278
),
8379
]
8480
)
85-
tessellation = mm.morphological_tessellation(
86-
df,
87-
)
81+
tessellation = mm.morphological_tessellation(df, simplify=False)
8882
assert (tessellation.geom_type == "Polygon").all()
8983
assert 144 not in tessellation.index
9084
assert len(tessellation) == len(df) - 1
9185

86+
@pytest.mark.skipif(not SHPLY_GE_210, reason="coverage_simplify required")
87+
def test_morphological_tessellation_simplify(self):
88+
simplified = mm.morphological_tessellation(
89+
self.df_buildings,
90+
)
91+
assert simplified.get_coordinates().shape[0] == 4557
92+
93+
dense = mm.morphological_tessellation(self.df_buildings, simplify=False)
94+
assert dense.get_coordinates().shape[0] == 47505
95+
9296
def test_enclosed_tessellation(self):
9397
tessellation = mm.enclosed_tessellation(
9498
self.df_buildings, self.enclosures.geometry, simplify=False
@@ -186,7 +190,7 @@ def test_verify_tessellation(self):
186190
]
187191
)
188192
tessellation = mm.morphological_tessellation(
189-
df, clip=self.df_streets.buffer(50)
193+
df, clip=self.df_streets.buffer(50), simplify=False
190194
)
191195
with (
192196
pytest.warns(
@@ -373,7 +377,7 @@ def test_multi_index(self):
373377
ValueError,
374378
match="MultiIndex is not supported in `momepy.morphological_tessellation`.",
375379
):
376-
mm.morphological_tessellation(buildings)
380+
mm.morphological_tessellation(buildings, simplify=False)
377381
with pytest.raises(
378382
ValueError,
379383
match="MultiIndex is not supported in `momepy.enclosed_tessellation`.",

0 commit comments

Comments
 (0)