Note

This page was generated from user_guide/combined/distribution.ipynb.
Interactive online version: Binder badge

Measuring spatial distribution#

Spatial distribution can be captured many ways. This notebook show couple of them, based on orientation and street corridor.

[1]:
import matplotlib.pyplot as plt
import momepy
import osmnx as ox
[2]:
gdf = ox.features_from_place("Kahla, Germany", tags={"building": True})
buildings = ox.projection.project_gdf(gdf).reset_index()

limit = momepy.buffered_limit(buildings)
tessellation = momepy.morphological_tessellation(buildings, clip=limit)
/Users/martin/miniforge3/envs/momepy/lib/python3.11/site-packages/osmnx/features.py:294: FutureWarning: The 'unary_union' attribute is deprecated, use the 'union_all()' method instead.
  polygon = gdf_place["geometry"].unary_union
[3]:
streets_graph = ox.graph_from_place("Kahla, Germany", network_type="drive")
streets_graph = ox.projection.project_graph(streets_graph)
edges = ox.graph_to_gdfs(
    streets_graph,
    nodes=False,
    edges=True,
    node_geometry=False,
    fill_edge_geometry=True,
).reset_index(drop=True)
/Users/martin/miniforge3/envs/momepy/lib/python3.11/site-packages/osmnx/graph.py:392: FutureWarning: The 'unary_union' attribute is deprecated, use the 'union_all()' method instead.
  polygon = gdf_place["geometry"].unary_union

Alignment#

We can measure alignment of different elements to their neighbours (for which Graph is needed) or to different elements. We will explore cell alignment (difference of orientation of buildings and cells) and street alignment (difference of orientation of buildings and street segments).

Cell alignment#

For cell_alignment we need to know orientations, so let’s calculate them first. Orientation is defined as an orientation of the longext axis of bounding rectangle in range [0,45). It captures the deviation of orientation from cardinal directions:

[4]:
buildings["orientation"] = momepy.orientation(buildings)
tessellation["orientation"] = momepy.orientation(tessellation)
[5]:
buildings.plot(
    column="orientation", legend=True, cmap="Spectral", figsize=(10, 10)
).set_axis_off()
../../_images/user_guide_combined_distribution_6_0.png

cell_alignment requires both orientation arrays:

[6]:
buildings["cell_align"] = momepy.cell_alignment(
    buildings.orientation, tessellation.orientation
)
[7]:
buildings.plot(
    column="cell_align", legend=True, cmap="Reds", figsize=(10, 10)
).set_axis_off()
../../_images/user_guide_combined_distribution_9_0.png

No really clear pattern is visible in this case, but it might be in other, especially comparing building orientation with plots.

Street alignment#

Street alignment works on the same principle as cell alignment. What we do not have at this moment is street index linked to buildings:

[8]:
buildings["street_index"] = momepy.get_nearest_street(buildings, edges)

street_alignment then requires orientations of both builings and streets and the index of the nearest street.

[9]:
buildings["str_align"] = momepy.street_alignment(
    buildings.orientation, momepy.orientation(edges), buildings["street_index"]
)
[10]:
ax = edges.plot(color="grey", linewidth=0.5, figsize=(10, 10))
buildings.plot(ax=ax, column="str_align", legend=True)
ax.set_axis_off()
../../_images/user_guide_combined_distribution_14_0.png

Street profile#

street_profile captures several characters at the same time. It generates a series of perpendicular ticks of set length and set spacing and returns mean widths of street profile, their standard deviation, mean height and its standard deviation, profile as a ratio of widht and height and degree of openness. If heights are not passed, it will not return them and profile. We will use Manhattan example to illustrate how it works. Building height column is converted to float and buildings are exploded to avoid multipolygons.

[11]:
point = (40.731603, -73.977857)
dist = 1000
gdf = ox.features_from_point(point, dist=dist, tags={"building": True})
buildings = ox.projection.project_gdf(gdf)
buildings = buildings[buildings.geom_type.isin(["Polygon", "MultiPolygon"])]
[12]:
def clean_heights(x):
    try:
        return float(x)
    except ValueError:
        return 0


buildings["height"] = buildings["height"].fillna(0).apply(clean_heights)
buildings = buildings.explode(ignore_index=True)
[13]:
streets_graph = ox.graph_from_point(point, dist, network_type="drive")
streets_graph = ox.projection.project_graph(streets_graph)
edges = ox.graph_to_gdfs(
    streets_graph,
    nodes=False,
    edges=True,
    node_geometry=False,
    fill_edge_geometry=True,
).reset_index()
[14]:
ax = buildings.plot(figsize=(10, 10), color="lightgrey")
edges.plot(ax=ax)
ax.set_axis_off()
../../_images/user_guide_combined_distribution_19_0.png
[15]:
profile = momepy.street_profile(edges, buildings, height=buildings["height"])

We can assign measrued characters as columns of edges gdf:

[16]:
edges[profile.columns] = profile
edges[profile.columns].head()
[16]:
width openness width_deviation height height_deviation hw_ratio
0 22.015871 0.416667 1.138172 16.178571 12.013273 0.734859
1 25.596680 0.233333 3.786193 23.421739 21.358412 0.915030
2 30.392740 0.555556 4.446756 20.625000 2.728029 0.678616
3 38.879126 0.750000 4.188487 19.000000 0.000000 0.488694
4 22.015871 0.416667 1.138172 16.178571 12.013273 0.734859
[17]:
f, axes = plt.subplots(figsize=(15, 25), ncols=2, nrows=3)
edges.plot(ax=axes[0][0], column="width", legend=True, cmap="Blues_r")
buildings.plot(ax=axes[0][0], color="lightgrey")
edges.plot(ax=axes[0][1], column="width_deviation", legend=True)
buildings.plot(ax=axes[0][1], color="lightgrey")
axes[0][0].set_axis_off()
axes[0][0].set_title("width")
axes[0][1].set_axis_off()
axes[0][1].set_title("width_deviation")
edges.plot(ax=axes[1][0], column="hw_ratio", legend=True, cmap="Spectral")
buildings.plot(ax=axes[1][0], color="lightgrey")
edges.plot(ax=axes[1][1], column="openness", legend=True, cmap="Greens")
buildings.plot(ax=axes[1][1], color="lightgrey")
axes[1][0].set_axis_off()
axes[1][0].set_title("profile")
axes[1][1].set_axis_off()
axes[1][1].set_title("openness")
edges.plot(ax=axes[2][0], column="height", legend=True, cmap="Reds")
buildings.plot(ax=axes[2][0], color="lightgrey")
edges.plot(ax=axes[2][1], column="height_deviation", legend=True)
buildings.plot(ax=axes[2][1], color="lightgrey")
axes[2][0].set_axis_off()
axes[2][0].set_title("height")
axes[2][1].set_axis_off()
axes[2][1].set_title("height_deviations")
[17]:
Text(0.5, 1.0, 'height_deviations')
../../_images/user_guide_combined_distribution_23_1.png