Adding roads to the graph

[1]:
import geopandas as gpd

import os
os.getcwd()

import pickle

Initialization

[2]:
local_crs = 32637 # Tula region crs

The graph can be obtained by transport_frames.graph.get_graph()

[3]:
with open("data/G_drive.pickle", "rb") as f:
    G_drive = pickle.load(f)
[4]:
from transport_frames.utils.helper_funcs import plot_graph

plot_graph(G_drive)
../_images/examples_4_road_adder_6_0.png

Load a file with the roads to add. They must contain ref_type and reg parameter

[5]:
new_roads = gpd.read_file("data/new_road_tula_region.geojson")
new_roads
[5]:
maxspeed highway ref reg node_start node_end geometry
0 1000.0 None None 1 None None LINESTRING (390444.88 6012584.465, 390410.011 ...
[6]:
plot_graph(edges = new_roads)
../_images/examples_4_road_adder_9_0.png

Road addition

[7]:
from transport_frames.road_adder import add_roads

new_road_graph = add_roads(G_drive, new_roads, local_crs)
[8]:
plot_graph(new_road_graph)
../_images/examples_4_road_adder_12_0.png

Check connectivity improvement

To see difference in travel time with new roads

[9]:
settlement_points = gpd.read_file("data/tula_region_towns.geojson")
settlement_polygons = gpd.read_file("data/tula_region_municipalities.geojson")
[10]:
from transport_frames.indicators import get_connectivity

con_old = get_connectivity(settlement_points,settlement_polygons,local_crs,graph = G_drive)
con_old.head()
2026-04-05 00:26:17.418 | WARNING  | Removing 197 nodes from 22 smaller strongly connected components. These are subgraphs where nodes are internally reachable but isolated from the rest. Retaining only the largest strongly connected component (31927 nodes).
[10]:
name geometry connectivity
0 Манаенское POLYGON ((36.27046 53.67571, 36.27343 53.67505... 145.007980
1 Астаповское POLYGON ((36.47061 53.63576, 36.48947 53.63114... 130.750246
2 Арсеньево POLYGON ((36.63005 53.72754, 36.63174 53.72702... 118.799943
3 Левобережное POLYGON ((35.89856 53.85018, 35.90015 53.84679... 159.656568
4 Правобережное POLYGON ((36.1455 53.80533, 36.14561 53.80468,... 153.267224
[11]:
con_new = get_connectivity(settlement_points,settlement_polygons,local_crs,graph = new_road_graph)
con_new.head()
2026-04-05 00:26:37.405 | WARNING  | Removing 197 nodes from 22 smaller strongly connected components. These are subgraphs where nodes are internally reachable but isolated from the rest. Retaining only the largest strongly connected component (31940 nodes).
[11]:
name geometry connectivity
0 Манаенское POLYGON ((36.27046 53.67571, 36.27343 53.67505... 140.871501
1 Астаповское POLYGON ((36.47061 53.63576, 36.48947 53.63114... 126.899580
2 Арсеньево POLYGON ((36.63005 53.72754, 36.63174 53.72702... 115.505683
3 Левобережное POLYGON ((35.89856 53.85018, 35.90015 53.84679... 156.128797
4 Правобережное POLYGON ((36.1455 53.80533, 36.14561 53.80468,... 149.911153
[12]:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches

cmp = con_old[["geometry", "connectivity"]].rename(
    columns={"connectivity": "connectivity_old"}
).join(
    con_new[["connectivity"]].rename(columns={"connectivity": "connectivity_new"}),
    how="inner",
)


cmp["delta"] = cmp["connectivity_new"] - cmp["connectivity_old"]

eps = 1e-9
cmp["change"] = np.select(
    [cmp["delta"] < -eps, cmp["delta"] > eps],
    ["decreased", "increased"],
    default="unchanged",
)

vmin = min(cmp["connectivity_old"].min(), cmp["connectivity_new"].min())
vmax = max(cmp["connectivity_old"].max(), cmp["connectivity_new"].max())

fig, ax = plt.subplots(1, 3, figsize=(24, 8))

cmp.plot(
    column="connectivity_old",
    cmap="RdYlGn_r",
    legend=True,
    linewidth=0.2,
    edgecolor="white",
    vmin=vmin,
    vmax=vmax,
    ax=ax[0],
    aspect="auto",
)
ax[0].set_title("Connectivity (old), min")
ax[0].set_axis_off()

cmp.plot(
    column="connectivity_new",
    cmap="RdYlGn_r",
    legend=True,
    linewidth=0.2,
    edgecolor="white",
    vmin=vmin,
    vmax=vmax,
    ax=ax[1],
    aspect="auto",
)
ax[1].set_title("Connectivity (new), min")
ax[1].set_axis_off()

color_map = {
    "decreased": "#2ca25f",
    "increased": "#de2d26",
    "unchanged": "#ffd92f",
}

for cls, color in color_map.items():
    part = cmp[cmp["change"] == cls]
    if part.empty:
        continue
    part.plot(
        color=color,
        linewidth=0.2,
        edgecolor="white",
        ax=ax[2],
        aspect="auto",
    )

ax[2].set_title("Connectivity change (new - old)")
ax[2].set_axis_off()
ax[2].legend(
    handles=[
        mpatches.Patch(color=color_map["decreased"], label="Time decreased"),
        mpatches.Patch(color=color_map["increased"], label="Time increased"),
        mpatches.Patch(color=color_map["unchanged"], label="No change"),
    ],
    loc="lower left",
)

plt.tight_layout()
../_images/examples_4_road_adder_17_0.png