Barriers#
[1]:
!pip install osmnx
Collecting osmnx
Using cached osmnx-2.1.0-py3-none-any.whl.metadata (4.7 kB)
Collecting geopandas>=1.0.1 (from osmnx)
Downloading geopandas-1.1.4-py3-none-any.whl.metadata (2.3 kB)
Requirement already satisfied: networkx>=2.5 in /opt/conda/lib/python3.12/site-packages (from osmnx) (3.4.2)
Requirement already satisfied: numpy>=1.24 in /opt/conda/lib/python3.12/site-packages (from osmnx) (2.2.6)
Requirement already satisfied: pandas>=1.5 in /opt/conda/lib/python3.12/site-packages (from osmnx) (2.2.3)
Requirement already satisfied: requests>=2.30 in /opt/conda/lib/python3.12/site-packages (from osmnx) (2.32.3)
Collecting shapely>=2.0 (from osmnx)
Using cached shapely-2.1.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl.metadata (6.8 kB)
Collecting pyogrio>=0.7.2 (from geopandas>=1.0.1->osmnx)
Downloading pyogrio-0.13.0-cp311-abi3-manylinux_2_28_x86_64.whl.metadata (5.8 kB)
Requirement already satisfied: packaging in /opt/conda/lib/python3.12/site-packages (from geopandas>=1.0.1->osmnx) (25.0)
Collecting pyproj>=3.5.0 (from geopandas>=1.0.1->osmnx)
Using cached pyproj-3.7.2-cp312-cp312-manylinux_2_28_x86_64.whl.metadata (31 kB)
Requirement already satisfied: python-dateutil>=2.8.2 in /opt/conda/lib/python3.12/site-packages (from pandas>=1.5->osmnx) (2.9.0.post0)
Requirement already satisfied: pytz>=2020.1 in /opt/conda/lib/python3.12/site-packages (from pandas>=1.5->osmnx) (2025.2)
Requirement already satisfied: tzdata>=2022.7 in /opt/conda/lib/python3.12/site-packages (from pandas>=1.5->osmnx) (2025.2)
Requirement already satisfied: certifi in /opt/conda/lib/python3.12/site-packages (from pyogrio>=0.7.2->geopandas>=1.0.1->osmnx) (2025.4.26)
Requirement already satisfied: six>=1.5 in /opt/conda/lib/python3.12/site-packages (from python-dateutil>=2.8.2->pandas>=1.5->osmnx) (1.17.0)
Requirement already satisfied: charset_normalizer<4,>=2 in /opt/conda/lib/python3.12/site-packages (from requests>=2.30->osmnx) (3.4.2)
Requirement already satisfied: idna<4,>=2.5 in /opt/conda/lib/python3.12/site-packages (from requests>=2.30->osmnx) (3.10)
Requirement already satisfied: urllib3<3,>=1.21.1 in /opt/conda/lib/python3.12/site-packages (from requests>=2.30->osmnx) (2.4.0)
Using cached osmnx-2.1.0-py3-none-any.whl (104 kB)
Downloading geopandas-1.1.4-py3-none-any.whl (343 kB)
Downloading pyogrio-0.13.0-cp311-abi3-manylinux_2_28_x86_64.whl (33.3 MB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 33.3/33.3 MB 88.9 MB/s eta 0:00:00ta 0:00:01
Using cached pyproj-3.7.2-cp312-cp312-manylinux_2_28_x86_64.whl (9.6 MB)
Using cached shapely-2.1.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (3.1 MB)
Installing collected packages: shapely, pyproj, pyogrio, geopandas, osmnx
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 5/5 [osmnx]32m3/5 [geopandas]
Successfully installed geopandas-1.1.4 osmnx-2.1.0 pyogrio-0.13.0 pyproj-3.7.2 shapely-2.1.2
[2]:
import sys
from pathlib import Path
repo_root = Path.cwd().resolve().parents[1]
sys.path.insert(0, str(repo_root))
import cityImage as ci
import matplotlib.pyplot as plt
import matplotlib.font_manager as fm
fm.fontManager.addfont("times.ttf")
plt.rcParams["font.family"] = "Times New Roman"
[3]:
import osmnx as ox
import pandas as pd
import geopandas as gpd
from matplotlib.colors import LinearSegmentedColormap
%matplotlib inline
import warnings
warnings.simplefilter(action="ignore")
Functions from the cityImage API used in this notebook#
This notebook identifies the structuring barriers of a city — major roads, railways, water features, coastlines and large parks — and assigns their relationship to the street segments. These barriers operationalise Lynch’s edges. Acquisition and file IO are delegated to OSMnx and GeoPandas; cityImage keeps the schema conversion and the barrier semantics.
barriers_from_osm — Download structuring barriers (water, railways, parks, major roads) from OpenStreetMap.
plot_gdf — Plot a single GeoDataFrame with cityImage styling.
network_from_osm — Download a street network from OpenStreetMap and return it as cityImage
nodesandedgesGeoDataFrames.clean_network — Clean and simplify the network: remove duplicate nodes/edges, pseudo-nodes, dead-ends and self-loops, and repair topology.
along_water — Flag street segments that run along water barriers.
along_within_parks — Flag street segments that run along or within parks.
barriers_along — Assign to each edge the barriers it runs along.
assign_structuring_barriers — Assign the crossing/structuring-barrier relationship to edges.
For the full list of functions and their parameters, see the API reference.
[4]:
# initialise path, names, etc.
city_name = 'Paris'
epsg = 27571
crs = 'EPSG:'+str(epsg)
place = 'Paris, Ile-de-France'
Barriers Identification#
Downloading from OSM
Choose between the following methods:
OSMplace, providing an OSM place name (e.g. City).polygon, providing a Polygon (coordinates must be in units of latitude-longitude degrees).distance_from_address, providing a precise address and setting thedistanceparameter.distance_from_point, providing point coordinates (in units of latitude-longitude degrees) and setting thedistanceparameter to build the bounding box around the point.
[5]:
download_method = 'OSMplace'
[6]:
barriers = ci.barriers_from_osm(
place,
download_method=download_method,
crs=crs,
include_primary=True,
include_secondary=False,
parks_min_area=100000,
)
barriers.reset_index(inplace=True, drop=True)
barriers["barrierID"] = barriers.index.astype(int)
[7]:
# Barriers are already combined by ci.barriers_from_osm().
Visualisation. The cell below maps the result using the cityImage plotting helpers such as plot_gdf; see the Plotting section of the API reference for all styling options.
[8]:
import matplotlib.pyplot as plt
plt.rcParams["font.family"] = "Times New Roman"
[9]:
barriers.sort_values(by = 'barrier_type', ascending = False, inplace = True)
colors = ['green', 'red', 'gray', 'blue']
cmap = LinearSegmentedColormap.from_list('cmap', colors, N=len(colors))
fig = ci.plot_gdf(gdf = barriers, column = 'barrier_type', black_background = False, title = city_name+': Barriers',
legend = True, cmap = cmap)
Incorporating Barriers into the Street Network#
This is an optional step that allows modelling the effect of barriers on pedestrian movement, for exmaple, in agent-based modelling, or in route-choice modelling.
Download the street network
Choose between the following methods:
OSMplace, providing an OSM place name (e.g. City).polygon, providing a Polygon (coordinates must be in units of latitude-longitude degrees).distance_from_address, providing a precise address and setting thedistanceparameter.distance_from_point, providing point coordinates (in units of latitude-longitude degrees) and setting thedistanceparameter to build the bounding box around the point.
Downloading the graph and cleaning it (see the notebook 01-Nodes_Paths_fromOSM for details on the cleaning process)
[ ]:
nodes_graph, edges_graph = ci.network_from_osm(place, download_method=download_method, network_type="walk", crs=crs)
nodes_graph, edges_graph = ci.clean_network(nodes_graph, edges_graph, dead_ends = True, remove_islands = True,
self_loops = True, same_vertexes_edges = True)
(or) Load it from local path
[12]:
input_path = '../output/'+city_name
nodes_graph = gpd.read_file(input_path+'_nodes.gpkg')
edges_graph = gpd.read_file(input_path+'_edges.gpkg')
nodes_graph.index, edges_graph.index = nodes_graph.nodeID, edges_graph.edgeID
nodes_graph.index.name, edges_graph.index.name = None, None
Visualisation. The cell below maps the result using the cityImage plotting helpers such as plot_gdf; see the Plotting section of the API reference for all styling options.
[13]:
fig = ci.plot_gdf(edges_graph, black_background = False, geometry_size = 1.0, alpha = 1.0,
color = 'black', title = city_name+': Street Network', figsize = (10,10))
Assigning barriers to street segments#
Type of Barriers:
Positive barriers, from a pedestrian perspective: Waterbodies, Parks.
Negative barriers, from a pedestrian perspective: Major Roads, Railway Structures.
Structuring barriers - Barriers which structure and shape the image of the city: Waterbodies, Major roads, Railways.
[14]:
# clipping barriers to case study area
envelope = edges_graph.unary_union.envelope
barriers_within = barriers[barriers.intersects(edges_graph.unary_union.envelope)]
Street Segments Along and Within Positive Barriers#
[15]:
sindex = edges_graph.sindex
# rivers
edges_graph = ci.along_water(edges_graph, barriers_within)
# parks
edges_graph = ci.along_within_parks(edges_graph, barriers_within)
# altogheter
edges_graph['p_barr'] = edges_graph['a_rivers']+edges_graph['w_parks']
edges_graph['p_barr'] = edges_graph.apply(lambda row: list(set(row['p_barr'])), axis = 1)
Street Segments Along Negative Barriers#
[16]:
tmp = barriers_within[barriers_within['barrier_type'].isin(['railway', 'road'])]
edges_graph['n_barr'] = edges_graph.apply(lambda row: ci.barriers_along(row['edgeID'], edges_graph, tmp, sindex,
offset = 25), axis = 1)
Street Segments Crossing any kind of barrier but parks: Structuring Barriers#
[17]:
edges_graph = ci.assign_structuring_barriers(edges_graph, barriers_within)
Visualisation. The cell below maps the result using the cityImage plotting helpers such as plot_gdf; see the Plotting section of the API reference for all styling options.
[18]:
# positive barriers
edges_graph['p_bool'] = edges_graph.apply(lambda row: len(row['p_barr']) > 0, axis = 1)
tmp = edges_graph[edges_graph.p_bool].copy()
# base map
base_map_dict = {'base_map_gdf': edges_graph, 'base_map_alpha' : 0.3, 'base_map_color' : 'black'}
fig = ci.plot_gdf(tmp, black_background = False, figsize = (8, 8), color = 'red', title = city_name+': Streets across/along parks and rivers',
legend = False, **base_map_dict)
[19]:
# negative barriers
edges_graph['n_bool'] = edges_graph.apply(lambda row: len(row['n_barr']) > 0, axis = 1)
tmp = edges_graph[edges_graph.n_bool].copy()
# base map
fig = ci.plot_gdf(tmp, black_background = False, figsize = (8, 8), color = 'red', title = city_name+': Streets along negative barriers',
legend = False, **base_map_dict)
[22]:
# separating barriers
tmp = edges_graph[edges_graph.sep_barr].copy()
fig = ci.plot_gdf(tmp, black_background = False, figsize = (8, 8), color = 'red',
title = city_name+': Streets crossing structuring barriers: water, highways, railways',
**base_map_dict)
Exporting. The results are written to disk as GeoPackage files with GeoPandas’ to_file, ready to be reused in later notebooks or in external GIS software.
[23]:
# saving barriers_gdf
output_path = '../output/'+city_name
barriers.to_file(output_path+"_barriers.gpkg", driver='GPKG')
# converting list fields to string
to_convert = ['a_rivers', 'w_parks','n_barr', 'p_barr']
edges_graph_string = edges_graph.copy()
for column in to_convert:
edges_graph_string[column] = edges_graph_string[column].astype(str)
edges_graph_string.to_file(output_path+"_edges_ped.gpkg", driver='GPKG')
nodes_graph.to_file(output_path+'_nodes_ped.gpkg', driver='GPKG')