Note

# Enclosed tessellation#

Enclosed tessellation is an enhanced morphological tessellation, based on predefined enclosures and building footprints. We can see enclosed tessellation as two-step partitioning of space based on building footprints and boundaries (e.g. street network, railway). Original morphological tessellation is used under the hood to partition each enclosure.

Note

Enclosed tessellation has been developed as a part of Urban Grammar AI research project which is publicly available and provides an example of real-world application of the concept.

In this notebook, we will look at the concept of *enclosures* behind enclosed tessellation, generate tessellation itself and compare it to a simpler morphological tessellation.

## Enclosures#

Enclosures are areas enclosed from all sides by at least one type of a barrier. Barriers are typically roads, railways, natural features like rivers and other water bodies or a coastline. In our example, we will work with roads, and illustrate the behaviour of additional barriers using artificial data.

```
[1]:
```

```
import geopandas as gpd
import momepy
```

```
[2]:
```

```
streets = gpd.read_file(momepy.datasets.get_path("bubenec"), layer="streets")
```

```
[3]:
```

```
streets.plot(figsize=(10, 10)).set_axis_off()
```

It is optimal (although not necessary) to specify the external boundary of the area for which we want to generate enclosures. In this case, we use a convex hull around our street network. In the case of islands, a typical limit is a coastline, but in most of the situations, it will the boundary of the case study area.

```
[4]:
```

```
convex_hull = streets.union_all().convex_hull
```

```
[5]:
```

```
ax = streets.plot(figsize=(10, 10))
gpd.GeoSeries([convex_hull.boundary]).plot(ax=ax, color="r")
ax.set_axis_off()
```

The `momepy.enclosures`

function requires `limit`

as geopandas.GeoSeries or GeoDataFrame and can contain multiple objects.

Generating enclosures is then straightforward:

```
[6]:
```

```
enclosures = momepy.enclosures(streets, limit=convex_hull)
```

```
[7]:
```

```
enclosures.plot(figsize=(10, 10), edgecolor="w").set_axis_off()
```

The resulting enclosures are a result of polygonization of the input. However, if there are `additional_barriers`

, polygons above are further subdivided. Let’s now pretend that two diagonals represent the railway as an additional barrier, and one horizontal line represents a river:

```
[8]:
```

```
import numpy as np
from shapely.geometry import LineString
b = convex_hull.bounds
railway = gpd.GeoSeries(
[
LineString([(b[0], b[1]), (b[2], b[3])]),
LineString([(b[0], b[3]), (b[2], b[1])]),
]
)
rivers = gpd.GeoSeries(
[
LineString(
[(b[0], np.mean([b[1], b[3]])), (b[2], np.mean([b[1], b[3]]))]
),
LineString(
[(np.mean([b[0], b[2]]), b[1]), (np.mean([b[0], b[2]]), b[3])]
),
]
)
```

```
[9]:
```

```
ax = streets.plot(figsize=(10, 10))
gpd.GeoSeries([convex_hull.boundary]).plot(ax=ax, color="r")
railway.plot(ax=ax, color="k")
rivers.plot(ax=ax, color="g")
ax.set_axis_off()
```

Enclosures are now defined using all the barriers.

```
[10]:
```

```
enclosures_additional = momepy.enclosures(
streets,
limit=convex_hull,
additional_barriers=[railway, rivers],
)
```

```
[11]:
```

```
enclosures_additional.plot(figsize=(10, 10), edgecolor="w").set_axis_off()
```

## Enclosed tessellation#

Having enclosures, we can now use enclosed tessellation. That, in principle, applies morphological tessellation to each enclosure using it as its limit.

For clarity, we will use the simpler enclosures we generated above.

```
[12]:
```

```
buildings = gpd.read_file(
momepy.datasets.get_path("bubenec"), layer="buildings"
)
```

```
[13]:
```

```
buildings.plot(figsize=(10, 10)).set_axis_off()
```

```
[14]:
```

```
enclosed_tess = momepy.enclosed_tessellation(buildings, enclosures)
```

```
[15]:
```

```
ax = enclosed_tess.plot(edgecolor="white", figsize=(10, 10))
buildings.plot(ax=ax, color="white", alpha=0.5)
ax.set_axis_off()
```

### Comparison to morphological tessellation#

Let’s not see how enclosed tessellation differs from morphological tessellation. First, we generate morphological tessellation within the same limit.

```
[16]:
```

```
morphological_tess = momepy.morphological_tessellation(
buildings, clip=convex_hull
)
```

```
[17]:
```

```
ax = morphological_tess.plot(edgecolor="white", figsize=(10, 10))
buildings.plot(ax=ax, color="white", alpha=0.5)
ax.set_axis_off()
```

We can immediately see that the enclosed tessellation is tidier and resembles plots. We can overlay both for a direct comparison.

```
[18]:
```

```
ax = morphological_tess.plot(
edgecolor="blue",
facecolor="none",
linestyle="dotted",
alpha=0.5,
figsize=(10, 10),
)
enclosed_tess.plot(
ax=ax, edgecolor="red", facecolor="none", linestyle="dotted", alpha=0.5
)
ax.set_axis_off()
```

From this figure, we can see that a large portion of geometry overlaps, but there are apparent differences when it comes to open spaces.

## Performance#

Enclosed tessellation usually is much faster and less demanding algorithm than morphological tessellation. Furthermore, it is by default parallelised using `joblib`

. If you do not want to use it, you can set `n_jobs=1`

to make a simple loop instead.

```
[19]:
```

```
enclosed_tess = momepy.enclosed_tessellation(buildings, enclosures, n_jobs=1)
```

## Enclosed tessellation based on OpenStretMap#

To illustrate a more real-life example, let’s try to generate tessellation based on a small town retrieved from OSM. We will use `osmnx`

package to get the data.

```
[20]:
```

```
import osmnx as ox
gdf = ox.features_from_place("Kahla, Germany", tags={"building": True})
buildings = ox.projection.project_gdf(gdf)
streets_graph = ox.graph_from_place("Kahla, Germany", network_type="drive")
streets_graph = ox.projection.project_graph(streets_graph)
streets = ox.graph_to_gdfs(
streets_graph,
nodes=False,
edges=True,
node_geometry=False,
fill_edge_geometry=True,
)
```

```
/Users/martin/miniforge3/envs/momepy/lib/python3.11/site-packages/osmnx/graph.py:392: DeprecationWarning: The 'unary_union' attribute is deprecated, use the 'union_all()' method instead.
polygon = gdf_place["geometry"].unary_union
```

```
[21]:
```

```
ax = buildings.plot(figsize=(10, 10))
streets.plot(ax=ax, color="k", linewidth=0.5)
ax.set_axis_off()
```

### Enclosures#

We will generate enclosures based on the street network and limit of using the buffer method. Before doing that we need to simplify the buildings GeoDataFrame.

Due to to the required indexing of tessellation cells without buildings, the `momepy.enclosed_tessellation`

does not accept MultiIndex data. To fix this we can drop the first level of the `buildings`

dataset Index.

```
[22]:
```

```
buildings = buildings.droplevel("element_type")
```

```
[23]:
```

```
limit = momepy.buffered_limit(buildings, buffer="adaptive")
enclosures = momepy.enclosures(streets, limit=limit)
```

At this moment, we have everything we need to generate enclosed tessellation.

### Enclosed tessellation#

```
[24]:
```

```
enclosed_tess = momepy.enclosed_tessellation(buildings, enclosures)
```

```
[25]:
```

```
ax = enclosed_tess.plot(figsize=(10, 10))
buildings.plot(ax=ax, color="white", alpha=0.5)
ax.set_axis_off()
```

Zooming closer:

```
[26]:
```

```
ax = enclosed_tess.plot(edgecolor="white", linewidth=0.2, figsize=(10, 10))
buildings.plot(ax=ax, color="white", alpha=0.5)
ax.set_axis_off()
ax.set_xlim(681500, 682500)
ax.set_ylim(5631000, 5632000)
```

```
[26]:
```

```
(5631000.0, 5632000.0)
```