Search
Shape

While the majority of momepy functions require the interaction of more GeoDataFrames or using spatial weights matrix, there are some which are calculated on single GeoDataFrame assessing the dimensions or shapes of features. This notebook illustrates how to measure simple shape characters.

import momepy
import geopandas as gpd
import matplotlib.pyplot as plt
import numpy as np

We will again use osmnx to get the data for our example and after preprocessing of building layer will generate tessellation. You can show the code with the button on the right side.

import osmnx as ox

gdf = ox.footprints.footprints_from_place(place='Kahla, Germany')
gdf_projected = ox.project_gdf(gdf)

buildings = momepy.preprocess(gdf_projected, size=30,
                              compactness=True, islands=True)
buildings['uID'] = momepy.unique_id(buildings)
limit = momepy.buffered_limit(buildings)
tess = momepy.Tessellation(buildings, unique_id='uID', limit=limit)
tessellation = tess.tessellation
Loop 1 out of 2.
Identifying changes: 100%|██████████| 2932/2932 [00:00<00:00, 3079.01it/s]
Changing geometry: 100%|██████████| 31/31 [00:00<00:00, 72.91it/s]
Loop 2 out of 2.
Identifying changes: 100%|██████████| 2520/2520 [00:00<00:00, 3685.87it/s]
Changing geometry: 100%|██████████| 2/2 [00:00<00:00, 60.78it/s]
Inward offset...
Discretization...
  2%|▏         | 42/2521 [00:00<00:05, 418.79it/s]
Generating input point array...
100%|██████████| 2521/2521 [00:03<00:00, 718.68it/s]
Generating Voronoi diagram...
Generating GeoDataFrame...
Vertices to Polygons: 100%|██████████| 267595/267595 [00:07<00:00, 36928.98it/s]
Dissolving Voronoi polygons...
Preparing limit for edge resolving...
Building R-tree...
  9%|▉         | 34/371 [00:00<00:01, 335.83it/s]
Identifying edge cells...
100%|██████████| 371/371 [00:01<00:00, 317.42it/s]
 21%|██        | 50/238 [00:00<00:00, 492.91it/s]
Cutting...
100%|██████████| 238/238 [00:00<00:00, 444.35it/s]
f, ax = plt.subplots(figsize=(10, 10))
tessellation.plot(ax=ax)
buildings.plot(ax=ax, color='white', alpha=.5)
ax.set_axis_off()
plt.show()

Building shapes

Few examples of measuring building shapes. Circular compactness measures the ratio of object area to the area of its smallest circumsribed circle:

blg_cc = momepy.CircularCompactness(buildings)
buildings['circular_com'] = blg_cc.series
f, ax = plt.subplots(figsize=(10, 10))
buildings.plot(ax=ax, column='circular_com', legend=True, cmap='viridis')
ax.set_axis_off()
plt.show()

While elongation is seen as elongation of its minimum bounding rectangle:

blg_elongation = momepy.Elongation(buildings)
buildings['elongation'] = blg_elongation.series
100%|██████████| 2518/2518 [00:01<00:00, 1592.01it/s]
f, ax = plt.subplots(figsize=(10, 10))
buildings.plot(ax=ax, column='elongation', legend=True, cmap='Blues_r')
ax.set_axis_off()
plt.show()

And squareness measures mean deviation of all corners from 90 degrees:

blg_squareness = momepy.Squareness(buildings)
buildings['squareness'] = blg_squareness.series
100%|██████████| 2518/2518 [00:01<00:00, 1886.55it/s]
f, ax = plt.subplots(figsize=(10, 10))
buildings.plot(ax=ax, column='squareness', legend=True, scheme='quantiles', cmap='Purples_r')
ax.set_axis_off()
plt.show()

For the form factor, we need to know the volume of each building. While we do not have building height data for Kahla, we will generate them randomly and pass a Series containing volume values to FormFactor.

Note: For the majority of parameters you can pass values as the name of the column, np.array, pd.Series or any other list-like object.

blg_volume = momepy.Volume(buildings, np.random.randint(4, 20, size=len(buildings)))
buildings['formfactor'] = momepy.FormFactor(buildings, volumes=blg_volume.series).series
f, ax = plt.subplots(figsize=(10, 10))
buildings.plot(ax=ax, column='formfactor', legend=True, scheme='quantiles', k=10, cmap='PuRd_r')
ax.set_axis_off()
plt.show()

Cell shapes

In theory, you can measure most of the 2D characters on all elements, including tessellation or blocks:

tes_cwa = momepy.CompactnessWeightedAxis(tessellation)
tessellation['cwa'] = tes_cwa.series
f, ax = plt.subplots(figsize=(10, 10))
tessellation.plot(ax=ax, column='cwa', legend=True, scheme='quantiles', k=10, cmap='Greens_r')
ax.set_axis_off()
plt.show()

Street network shapes

There are some characters which requires street network as an input. We can again use osmnx to retrieve it from OSM.

streets_graph = ox.graph_from_place('Kahla, Germany', network_type='drive')
streets_graph = ox.project_graph(streets_graph)

osmnx returns networkx Graph. While momepy works with graph in some cases, for this one we need GeoDataFrame. To get it, we can use ox.save_load.graph_to_gdfs.

Note: momepy.nx_to_gdf might work as well, but OSM network needs to be complete in that case. osmnx takes care of it.

edges = ox.save_load.graph_to_gdfs(streets_graph, nodes=False, edges=True,
                                   node_geometry=False, fill_edge_geometry=True)
f, ax = plt.subplots(figsize=(10, 10))
edges.plot(ax=ax, color='pink')
buildings.plot(ax=ax, color='lightgrey')
ax.set_axis_off()
plt.show()

Now we can calculate linearity of each segment:

edg_lin = momepy.Linearity(edges)
edges['linearity'] = edg_lin.series
100%|██████████| 410/410 [00:00<00:00, 3897.12it/s]
f, ax = plt.subplots(figsize=(10, 10))
edges.plot(ax=ax, column='linearity', legend=True, cmap='coolwarm_r', scheme='quantiles', k=4)
buildings.plot(ax=ax, color='lightgrey')
ax.set_axis_off()
plt.show()