Note

This page was generated from user_guide/elements/blocks.ipynb.
Interactive online version: Binder badge

Tessellation-based blocks#

Ideal case with ideal data#

Assume following situation: We have a layer of buildings for selected city, a street network represented by centrelines, we have generated morphological tessellation, but now we would like to do some work on block scale. We have two options - generate blocks based on street network (each closed loop is a block) or use morphological tessellation and join those cells, which are expected to be part of one block. In that sense, you will get tessellation-based blocks which are following the same logic as the rest of your data and are hence fully compatible. With momepy you can do that using momepy.generate_blocks.

[1]:
import geopandas as gpd
import momepy

For illustration, we can use bubenec dataset embedded in momepy.

[2]:
path = momepy.datasets.get_path("bubenec")
buildings = gpd.read_file(path, layer="buildings")
streets = gpd.read_file(path, layer="streets")
tessellation = gpd.read_file(path, layer="tessellation")
[3]:
ax = tessellation.plot(edgecolor="white", linewidth=0.2, figsize=(8, 8))
buildings.plot(ax=ax, color="white", alpha=0.5)
streets.plot(ax=ax, color="black")
ax.set_axis_off()
../../_images/user_guide_elements_blocks_4_0.png

This example is useful for illustration why pure network-based blocks are not ideal. 1. In the centre of the area would be the block, while it is clear that there is only open space. 2. Blocks on the edges of the area would be complicated, as streets do not fully enclose them.

None of it is an issue for tessellation-based blocks. momepy.generate_blocks requires tessellation, buildings, and streets. We have it all, so we can give it a try.

[4]:
blocks, tessellation_id = momepy.generate_blocks(
    tessellation, streets, buildings
)

The function returns a tuple of blocks, tessellation_id, where the first are the new geometries and the second is an array linking blocks to tessellation. You can use tessellation_id to link buildings to blocks directly, by filtering out all the negive(tessellation cells without buildings) ETCs.

[5]:
buildings["bID"] = tessellation_id[tessellation_id.index >= 0]
tessellation["bID"] = tessellation_id
[6]:
ax = blocks.plot(edgecolor="white", linewidth=0.5, figsize=(8, 8))
buildings.plot(ax=ax, color="white", alpha=0.5)
ax.set_axis_off()
../../_images/user_guide_elements_blocks_9_0.png

Fixing the street network#

The example above shows how it works in the ideal case - streets are attached, there are no gaps. However, that is often not true. For that reason, momepy includes momepy.extend_lines utility which is supposed to fix the issue. It can do two types of network adaptation:

  1. Snap network onto the network where false dead-end is present. It will extend the last segment by set distance and snap it onto the first reached segment.

  2. Snap network onto the edge of tessellation. As we need to be able to define blocks until the very edge of tessellation, in some cases, it is necessary to extend the segment and snap it to the edge of the tessellated area.

Let’s adapt our ideal street network and break it to illustrate the issue.

[7]:
import shapely

geom = streets.iloc[33].geometry
splitted = shapely.ops.split(geom, shapely.geometry.Point(geom.coords[5]))
streets.loc[33, "geometry"] = splitted.geoms[1]
geom = streets.iloc[19].geometry
splitted = shapely.ops.split(geom, geom.representative_point())
streets.loc[19, "geometry"] = splitted.geoms[0]

ax = tessellation.plot(edgecolor="white", linewidth=0.2, figsize=(8, 8))
buildings.plot(ax=ax, color="white", alpha=0.5)
streets.plot(ax=ax, color="black")
ax.set_axis_off()
../../_images/user_guide_elements_blocks_11_0.png

In this example, one of the lines on the right side is not snapped onto the network and other on the top-left leaves the gap between the edge of the tessellation. Let’s see what would be the result of blocks without any adaptation of this network.

[8]:
buildings = buildings.drop(["bID"], axis=1)
tessellation = tessellation.drop(["bID"], axis=1)
[9]:
blocks, tessellation_id = momepy.generate_blocks(
    tessellation, streets, buildings
)
[10]:
ax = blocks.plot(edgecolor="white", linewidth=0.5, figsize=(8, 8))
buildings.plot(ax=ax, color="white", alpha=0.5)
ax.set_axis_off()
../../_images/user_guide_elements_blocks_15_0.png

We can see that blocks are merged. To avoid this effect, we will first use momepy.extend_lines and then generate blocks.

[11]:
snapped = momepy.extend_lines(
    streets, tolerance=40, target=tessellation, barrier=buildings
)
[12]:
ax = tessellation.plot(edgecolor="white", linewidth=0.2, figsize=(8, 8))
buildings.plot(ax=ax, color="white", alpha=0.5)
snapped.plot(ax=ax, color="black")
ax.set_axis_off()
../../_images/user_guide_elements_blocks_18_0.png

What should be snapped is now snapped. We might have noticed that we used buildings int the momepy.extend_lines. That is to avoid extending segments through buildings.

With a fixed network, we can then generate correct blocks.

[13]:
blocks, tessellation_id = momepy.generate_blocks(
    tessellation, snapped, buildings
)
[14]:
ax = blocks.plot(edgecolor="white", linewidth=0.5, figsize=(8, 8))
buildings.plot(ax=ax, color="white", alpha=0.5)
ax.set_axis_off()
../../_images/user_guide_elements_blocks_21_0.png