Note

This page was generated from user_guide/graph/convert.ipynb.
Interactive online version: Binder badge

Converting from GeoDataFrame to Graph and backΒΆ

The model situation expects to have all input data for analysis in GeoDataFrames, including street network (e.g. from shapefile).

[1]:
import momepy
import geopandas as gpd
import matplotlib.pyplot as plt
import networkx as nx
[2]:
streets = gpd.read_file(momepy.datasets.get_path('bubenec'), layer='streets')
[3]:
f, ax = plt.subplots(figsize=(10, 10))
streets.plot(ax=ax)
ax.set_axis_off()
plt.show()
../../_images/user_guide_graph_convert_3_0.png

We have to convert this LineString GeoDataFrame to networkx.Graph. We use momepy.gdf_to_nx and later momepy.nx_to_gdf as pair of interconnected functions. gdf_to_nx supports both primal and dual graphs. Primal approach will save length of each segment to be used as a weight later, while dual will save the angle between segments (allowing angular centrality).

[4]:
graph = momepy.gdf_to_nx(streets, approach='primal')
[14]:
f, ax = plt.subplots(1, 3, figsize=(18, 6), sharex=True, sharey=True)
streets.plot(color='#e32e00', ax=ax[0])
for i, facet in enumerate(ax):
    facet.set_title(("Streets", "Primal graph", "Overlay")[i])
    facet.axis("off")
nx.draw(graph, {n:[n[0], n[1]] for n in list(graph.nodes)}, ax=ax[1], node_size=15)
streets.plot(color='#e32e00', ax=ax[2], zorder=-1)
nx.draw(graph, {n:[n[0], n[1]] for n in list(graph.nodes)}, ax=ax[2], node_size=15)
../../_images/user_guide_graph_convert_6_0.png
[6]:
dual = momepy.gdf_to_nx(streets, approach='dual')
[16]:
f, ax = plt.subplots(1, 3, figsize=(18, 6), sharex=True, sharey=True)
streets.plot(color='#e32e00', ax=ax[0])
for i, facet in enumerate(ax):
    facet.set_title(("Streets", "Dual graph", "Overlay")[i])
    facet.axis("off")
nx.draw(dual, {n:[n[0], n[1]] for n in list(dual.nodes)}, ax=ax[1], node_size=15)
streets.plot(color='#e32e00', ax=ax[2], zorder=-1)
nx.draw(dual, {n:[n[0], n[1]] for n in list(dual.nodes)}, ax=ax[2], node_size=15)
../../_images/user_guide_graph_convert_8_0.png

At this moment (almost) any networkx method can be used. For illustration, we will measure the node degree. Using networkx, we can do:

[7]:
degree = dict(nx.degree(graph))
nx.set_node_attributes(graph, degree, 'degree')

However, node degree is implemented in momepy so we can use directly:

[8]:
graph = momepy.node_degree(graph, name='degree')

Once we have finished our network-based analysis, we want to convert the graph back to geodataframe. For that, we will use momepy.nx_to_gdf, which gives us several options what to export.

  • lines

    • original LineString geodataframe

  • points

    • point geometry representing street network intersections (nodes of primal graph)

  • spatial_weights

    • spatial weights for nodes capturing their relationship within a network

Moreover, edges will contain node_start and node_end columns capturing the ID of both nodes at its ends.

[9]:
nodes, edges, sw = momepy.nx_to_gdf(graph, points=True, lines=True,
                                    spatial_weights=True)
[10]:
f, ax = plt.subplots(figsize=(10, 10))
nodes.plot(ax=ax, column='degree', cmap='tab20b', markersize=(nodes['degree'] * 100), zorder=2)
edges.plot(ax=ax, color='lightgrey', zorder=1)
ax.set_axis_off()
plt.show()

../../_images/user_guide_graph_convert_15_0.png
[11]:
nodes.head(3)
[11]:
degree nodeID geometry
0 3 1 POINT (1603585.640 6464428.774)
1 5 2 POINT (1603413.206 6464228.730)
2 3 3 POINT (1603268.502 6464060.781)
[12]:
edges.head(3)
[12]:
geometry mm_len node_start node_end
0 LINESTRING (1603585.640 6464428.774, 1603413.2... 264.103950 1 2
1 LINESTRING (1603561.740 6464494.467, 1603564.6... 70.020202 1 9
2 LINESTRING (1603585.640 6464428.774, 1603603.0... 88.924305 1 7