Note

Interactive online version:

# 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 momepy
import geopandas as gpd

[2]:

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

/Users/martin/Git/geopandas/geopandas/geodataframe.py:580: RuntimeWarning: Sequential read of iterator was interrupted. Resetting iterator. This can negatively impact the performance.
for feature in features_lst:

[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.unary_union.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=gpd.GeoSeries([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(
)

[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')

/Users/martin/Git/geopandas/geopandas/geodataframe.py:580: RuntimeWarning: Sequential read of iterator was interrupted. Resetting iterator. This can negatively impact the performance.
for feature in features_lst:

[13]:

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

[14]:

enclosed_tess = momepy.Tessellation(buildings, unique_id='uID', enclosures=enclosures).tessellation

[15]:

ax = enclosed_tess.plot(edgecolor='white', figsize=(10, 10))
buildings.plot(ax=ax, color='white', alpha=.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.Tessellation(buildings, unique_id='uID', limit=convex_hull, verbose=False).tessellation

[17]:

ax = morphological_tess.plot(edgecolor='white', figsize=(10, 10))
buildings.plot(ax=ax, color='white', alpha=.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=.5, figsize=(10, 10))
enclosed_tess.plot(ax=ax, edgecolor='red', facecolor='none', linestyle='dotted', alpha=.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 dask. If you do not have dask installed in your environment or do not want to use it, you can set use_dask=False to make a simple loop instead.

[19]:

enclosed_tess = momepy.Tessellation(buildings, unique_id='uID', enclosures=enclosures, use_dask=False)


## 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.

[2]:

import osmnx as ox

gdf = ox.geometries.geometries_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)

[21]:

ax = buildings.plot(figsize=(10, 10))
streets.plot(ax=ax, color='k', linewidth=.5)
ax.set_axis_off()


### Enclosures#

We will generate enclosures based on the street network and limit of using the buffer method.

[3]:

limit = momepy.buffered_limit(buildings)
enclosures = momepy.enclosures(streets, limit=gpd.GeoSeries([limit]))


Tessellation requires unique_id to match resulting cells to original buildings. We will generate a unique ID using momepy.unique_id.

[4]:

buildings['uID'] = momepy.unique_id(buildings)


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

### Enclosed tessellation#

[5]:

enclosed_tess = momepy.Tessellation(buildings, unique_id='uID', enclosures=enclosures).tessellation

[25]:

ax = enclosed_tess.plot(figsize=(10, 10))
buildings.plot(ax=ax, color='white', alpha=.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=.5)
ax.set_axis_off()
ax.set_xlim(681500, 682500)
ax.set_ylim(5631000, 5632000)

[26]:

(5631000.0, 5632000.0)