Landuse accessibility from OSM data

Calculate landuse accessibilities from OSM data.

This examples calculates landuse accessibility to pubs and restaurants for London using OpenStreetMap data.

from cityseer.metrics import layers
from cityseer.tools import graphs, io
import matplotlib.pyplot as plt
from osmnx import features

To start, follow the same approach as shown in the network examples to create the network.

lng, lat = -0.13396079424572427, 51.51371088849723
buffer = 1500
poly_wgs, epsg_code = io.buffered_point_poly(lng, lat, buffer)
G = io.osm_graph_from_poly(poly_wgs)
G_dual = graphs.nx_to_dual(G)
nodes_gdf, _edges_gdf, network_structure = io.network_structure_from_nx(G_dual)
WARNING:cityseer.tools.io:Merging node 12450391665 into 25544116 due to identical x, y coords.
WARNING:cityseer.tools.io:Unable to parse level info: -`;-4
WARNING:cityseer.tools.io:Unable to parse level info: -`;-4
WARNING:cityseer.tools.io:Unable to parse level info: -`;-4
INFO:cityseer.tools.graphs:Generating interpolated edge geometries.
100%|██████████| 24093/24093 [00:00<00:00, 51290.61it/s]
INFO:cityseer.tools.io:Converting networkX graph to CRS code 32630.
INFO:cityseer.tools.io:Processing node x, y coordinates.
100%|██████████| 22376/22376 [00:00<00:00, 1132845.84it/s]
INFO:cityseer.tools.io:Processing edge geom coordinates, if present.
100%|██████████| 24093/24093 [00:01<00:00, 16990.13it/s]
INFO:cityseer.tools.graphs:Removing filler nodes.
100%|██████████| 22376/22376 [00:00<00:00, 23375.08it/s]
INFO:cityseer.tools.util:Creating edges STR tree.
100%|██████████| 12155/12155 [00:00<00:00, 1573899.89it/s]
100%|██████████| 12155/12155 [00:01<00:00, 10470.36it/s]
INFO:cityseer.tools.graphs:Removing filler nodes.
100%|██████████| 10438/10438 [00:00<00:00, 64395.92it/s]
100%|██████████| 7386/7386 [00:00<00:00, 311266.70it/s]
INFO:cityseer.tools.graphs:Removing dangling nodes.
INFO:cityseer.tools.graphs:Removing filler nodes.
100%|██████████| 9004/9004 [00:00<00:00, 992940.87it/s]
INFO:cityseer.tools.util:Creating edges STR tree.
100%|██████████| 6652/6652 [00:00<00:00, 1432705.67it/s]
INFO:cityseer.tools.graphs:Splitting opposing edges.
100%|██████████| 5270/5270 [00:00<00:00, 167212.46it/s]
INFO:cityseer.tools.graphs:Squashing opposing nodes
INFO:cityseer.tools.graphs:Merging parallel edges within buffer of 25.
100%|██████████| 6661/6661 [00:00<00:00, 162107.52it/s]
INFO:cityseer.tools.util:Creating edges STR tree.
100%|██████████| 6612/6612 [00:00<00:00, 1768895.14it/s]
INFO:cityseer.tools.graphs:Splitting opposing edges.
100%|██████████| 5270/5270 [00:00<00:00, 38747.78it/s]
INFO:cityseer.tools.graphs:Squashing opposing nodes
INFO:cityseer.tools.graphs:Merging parallel edges within buffer of 25.
100%|██████████| 6668/6668 [00:00<00:00, 315822.02it/s]
INFO:cityseer.tools.util:Creating edges STR tree.
100%|██████████| 6658/6658 [00:00<00:00, 1637581.42it/s]
INFO:cityseer.tools.graphs:Splitting opposing edges.
100%|██████████| 5270/5270 [00:00<00:00, 152669.73it/s]
INFO:cityseer.tools.graphs:Squashing opposing nodes
INFO:cityseer.tools.graphs:Merging parallel edges within buffer of 25.
100%|██████████| 6659/6659 [00:00<00:00, 65366.97it/s]
INFO:cityseer.tools.util:Creating edges STR tree.
100%|██████████| 6659/6659 [00:00<00:00, 1380616.43it/s]
INFO:cityseer.tools.graphs:Splitting opposing edges.
100%|██████████| 5270/5270 [00:00<00:00, 95310.29it/s]
INFO:cityseer.tools.graphs:Squashing opposing nodes
INFO:cityseer.tools.graphs:Merging parallel edges within buffer of 25.
100%|██████████| 6660/6660 [00:00<00:00, 65674.52it/s]
INFO:cityseer.tools.util:Creating nodes STR tree
100%|██████████| 5270/5270 [00:00<00:00, 60563.67it/s]
INFO:cityseer.tools.graphs:Consolidating nodes.
100%|██████████| 5270/5270 [00:00<00:00, 7267.67it/s]
INFO:cityseer.tools.graphs:Merging parallel edges within buffer of 25.
100%|██████████| 6608/6608 [00:00<00:00, 264152.73it/s]
INFO:cityseer.tools.graphs:Removing filler nodes.
100%|██████████| 5230/5230 [00:00<00:00, 456614.35it/s]
INFO:cityseer.tools.util:Creating nodes STR tree
100%|██████████| 5158/5158 [00:00<00:00, 58115.11it/s]
INFO:cityseer.tools.graphs:Consolidating nodes.
100%|██████████| 5158/5158 [00:06<00:00, 759.84it/s] 
INFO:cityseer.tools.graphs:Merging parallel edges within buffer of 25.
100%|██████████| 6059/6059 [00:00<00:00, 72499.40it/s]
INFO:cityseer.tools.graphs:Removing filler nodes.
100%|██████████| 4798/4798 [00:00<00:00, 501002.55it/s]
INFO:cityseer.tools.util:Creating nodes STR tree
100%|██████████| 4762/4762 [00:00<00:00, 24404.47it/s]
INFO:cityseer.tools.graphs:Consolidating nodes.
100%|██████████| 4762/4762 [00:01<00:00, 3349.55it/s]
INFO:cityseer.tools.graphs:Merging parallel edges within buffer of 25.
100%|██████████| 5851/5851 [00:00<00:00, 219079.73it/s]
INFO:cityseer.tools.graphs:Removing filler nodes.
100%|██████████| 4708/4708 [00:00<00:00, 1004925.36it/s]
INFO:cityseer.tools.util:Creating nodes STR tree
100%|██████████| 4694/4694 [00:00<00:00, 60766.94it/s]
INFO:cityseer.tools.graphs:Consolidating nodes.
100%|██████████| 4694/4694 [00:02<00:00, 2234.60it/s]
INFO:cityseer.tools.graphs:Merging parallel edges within buffer of 25.
100%|██████████| 5710/5710 [00:00<00:00, 325705.84it/s]
INFO:cityseer.tools.graphs:Removing filler nodes.
100%|██████████| 4613/4613 [00:00<00:00, 805643.09it/s]
INFO:cityseer.tools.util:Creating nodes STR tree
100%|██████████| 4598/4598 [00:00<00:00, 60209.14it/s]
INFO:cityseer.tools.util:Creating edges STR tree.
100%|██████████| 5687/5687 [00:00<00:00, 1604210.56it/s]
INFO:cityseer.tools.graphs:Snapping gapped endings.
100%|██████████| 4598/4598 [00:00<00:00, 18916.69it/s]
INFO:cityseer.tools.util:Creating edges STR tree.
100%|██████████| 5888/5888 [00:00<00:00, 1774524.82it/s]
INFO:cityseer.tools.graphs:Splitting opposing edges.
100%|██████████| 4598/4598 [00:00<00:00, 16279.57it/s]
INFO:cityseer.tools.graphs:Merging parallel edges within buffer of 25.
100%|██████████| 6767/6767 [00:00<00:00, 352796.80it/s]
INFO:cityseer.tools.graphs:Removing dangling nodes.
100%|██████████| 5088/5088 [00:00<00:00, 713733.07it/s]
INFO:cityseer.tools.graphs:Removing filler nodes.
100%|██████████| 4288/4288 [00:00<00:00, 32345.17it/s]
INFO:cityseer.tools.util:Creating edges STR tree.
100%|██████████| 4811/4811 [00:00<00:00, 1686767.24it/s]
INFO:cityseer.tools.graphs:Splitting opposing edges.
100%|██████████| 3133/3133 [00:00<00:00, 11938.20it/s]
INFO:cityseer.tools.graphs:Squashing opposing nodes
INFO:cityseer.tools.graphs:Merging parallel edges within buffer of 25.
100%|██████████| 4851/4851 [00:00<00:00, 100570.25it/s]
INFO:cityseer.tools.util:Creating nodes STR tree
100%|██████████| 3133/3133 [00:00<00:00, 62204.75it/s]
INFO:cityseer.tools.graphs:Consolidating nodes.
100%|██████████| 3133/3133 [00:10<00:00, 298.19it/s]
INFO:cityseer.tools.graphs:Merging parallel edges within buffer of 25.
100%|██████████| 3876/3876 [00:00<00:00, 46089.68it/s]
INFO:cityseer.tools.util:Creating edges STR tree.
100%|██████████| 3680/3680 [00:00<00:00, 1693552.64it/s]
INFO:cityseer.tools.graphs:Splitting opposing edges.
100%|██████████| 2473/2473 [00:00<00:00, 10257.75it/s]
INFO:cityseer.tools.graphs:Squashing opposing nodes
INFO:cityseer.tools.graphs:Merging parallel edges within buffer of 25.
100%|██████████| 3690/3690 [00:00<00:00, 403277.44it/s]
INFO:cityseer.tools.util:Creating nodes STR tree
100%|██████████| 2473/2473 [00:00<00:00, 59565.93it/s]
INFO:cityseer.tools.graphs:Consolidating nodes.
100%|██████████| 2473/2473 [00:06<00:00, 392.17it/s]
INFO:cityseer.tools.graphs:Merging parallel edges within buffer of 25.
100%|██████████| 3165/3165 [00:00<00:00, 59942.17it/s]
INFO:cityseer.tools.graphs:Removing filler nodes.
100%|██████████| 2028/2028 [00:00<00:00, 105268.97it/s]
INFO:cityseer.tools.graphs:Merging parallel edges within buffer of 50.
100%|██████████| 2930/2930 [00:00<00:00, 150827.95it/s]
INFO:cityseer.tools.graphs:Ironing edges.
100%|██████████| 2911/2911 [00:00<00:00, 23159.06it/s]
INFO:cityseer.tools.graphs:Merging parallel edges within buffer of 1.
100%|██████████| 2898/2898 [00:00<00:00, 355547.23it/s]
INFO:cityseer.tools.graphs:Removing dangling nodes.
100%|██████████| 1887/1887 [00:00<00:00, 901133.06it/s]
INFO:cityseer.tools.graphs:Removing filler nodes.
100%|██████████| 1869/1869 [00:00<00:00, 320135.34it/s]
INFO:cityseer.tools.graphs:Converting graph to dual.
INFO:cityseer.tools.graphs:Preparing dual nodes
100%|██████████| 2845/2845 [00:00<00:00, 44313.05it/s]
INFO:cityseer.tools.graphs:Preparing dual edges (splitting and welding geoms)
100%|██████████| 2845/2845 [00:04<00:00, 682.84it/s] 
INFO:cityseer.tools.io:Preparing node and edge arrays from networkX graph.
100%|██████████| 2845/2845 [00:00<00:00, 19500.36it/s]
100%|██████████| 2845/2845 [00:00<00:00, 4317.47it/s]

Prepare the amenities GeoDataFrame by downloading the data from OpenStreetMap. The osmnx features_from_polygon works well for this purpose. In this instance, we are specifically targeting features that are labelled as an amenity type of either pub or restaurant. You can use the same idea to extract other features or land use types.

It is important to convert the derivative GeoDataFrame to the same CRS as the network.

data_gdf = features.features_from_polygon(
    poly_wgs, tags={"amenity": ["pub", "restaurant"]}
)
data_gdf = data_gdf.to_crs(nodes_gdf.crs)
data_gdf
geometry addr:city addr:country addr:housenumber addr:postcode addr:street amenity check_date contact:email contact:phone ... access:conditional note:access name:gsw note:name:en note:name:zh website:en serving_system:buffet building:use floor type
element id
node 21593236 POINT (699869.619 5711280.678) London GB 12 WC2A 3HP Gate Street pub 2023-04-18 info@theshiptavern.co.uk +44 20 7405 1992 ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
25471087 POINT (699749.16 5710787.29) London NaN 39 NaN Bow Street pub NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
25475914 POINT (699606.322 5711699.239) NaN NaN NaN NaN NaN pub NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
25746185 POINT (700308.36 5710758.42) London NaN NaN WC2R 3LD NaN pub NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
26699558 POINT (699071.215 5711168.671) NaN NaN NaN NaN NaN pub 2024-09-10 NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
way 995954627 POLYGON ((699270.101 5710540.727, 699277.571 5... London NaN 24 WC2H 0HX Charing Cross Road restaurant 2024-10-07 NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
1016903732 POLYGON ((699039.571 5710804.095, 699036.453 5... NaN NaN NaN NaN NaN restaurant NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
1060462032 POLYGON ((700144.125 5710144.723, 700142.327 5... London NaN NaN SE1 8XX Belvedere Road restaurant NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN retail NaN NaN
1072559143 POLYGON ((699383.906 5710631.863, 699382.376 5... London NaN 17 WC2E 9AX Garrick Street restaurant NaN NaN +44 20 3675 0930 ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
1194233515 POLYGON ((700109.233 5711375.71, 700111.379 57... London NaN 283-288 WC1V 7HP High Holborn pub 2023-08-03 NaN +44 20 7242 5669 ... NaN NaN NaN NaN NaN NaN NaN NaN Ground NaN

1431 rows × 253 columns

Some preparatory data cleaning is typically necessary. This example extracts the particular rows and columns of interest for the subsequent steps of analysis.

# extract nodes
data_gdf = data_gdf.loc["node"]
# reset index
data_gdf = data_gdf.reset_index(level=0, drop=True)
# extract relevant columns
data_gdf = data_gdf[["amenity", "geometry"]]
data_gdf.head()
amenity geometry
0 pub POINT (699869.619 5711280.678)
1 pub POINT (699749.16 5710787.29)
2 pub POINT (699606.322 5711699.239)
3 pub POINT (700308.36 5710758.42)
4 pub POINT (699071.215 5711168.671)

Once the land use and network data has been prepared, use the layers.compute_accessibilities method to compute accessibilities to land uses. The landuse_column_label and the target accessibility keys should correspond to the data in the input GeoDataFrame.

# compute pub accessibility
distances = [100, 200, 400, 800]
nodes_gdf, data_gdf = layers.compute_accessibilities(
    data_gdf,
    landuse_column_label="amenity",
    accessibility_keys=["pub", "restaurant"],
    nodes_gdf=nodes_gdf,
    network_structure=network_structure,
    distances=distances,
)
INFO:cityseer.metrics.layers:Computing land-use accessibility for: pub, restaurant
INFO:cityseer.metrics.layers:Assigning data to network.
100%|██████████| 701/701 [00:00<00:00, 122370.96it/s]
100%|██████████| 2845/2845 [00:01<00:00, 2793.31it/s]
INFO:cityseer.config:Metrics computed for:
INFO:cityseer.config:Distance: 100m, Beta: 0.04, Walking Time: 1.25 minutes.
INFO:cityseer.config:Distance: 200m, Beta: 0.02, Walking Time: 2.5 minutes.
INFO:cityseer.config:Distance: 400m, Beta: 0.01, Walking Time: 5.0 minutes.
INFO:cityseer.config:Distance: 800m, Beta: 0.005, Walking Time: 10.0 minutes.

The output columns are named cc_{key}_{distance}_{type}. Where the keys will correspond to the input accessibility keys and the distances will correspond to the input distances. The types will correspond to either non-weighted nw or distance weighted wt count, or else the distance to the nearest instance of a given landuse.

Standard GeoPandas functionality can be used to explore, visualise, or save the results.

nodes_gdf.columns
Index(['ns_node_idx', 'x', 'y', 'live', 'weight', 'primal_edge',
       'primal_edge_node_a', 'primal_edge_node_b', 'primal_edge_idx',
       'dual_node', 'cc_pub_100_nw', 'cc_pub_100_wt', 'cc_pub_200_nw',
       'cc_pub_200_wt', 'cc_pub_400_nw', 'cc_pub_400_wt', 'cc_pub_800_nw',
       'cc_pub_800_wt', 'cc_pub_nearest_max_800', 'cc_restaurant_100_nw',
       'cc_restaurant_100_wt', 'cc_restaurant_200_nw', 'cc_restaurant_200_wt',
       'cc_restaurant_400_nw', 'cc_restaurant_400_wt', 'cc_restaurant_800_nw',
       'cc_restaurant_800_wt', 'cc_restaurant_nearest_max_800'],
      dtype='object')
fig, ax = plt.subplots(1, 1, figsize=(8, 8), facecolor="#1d1d1d")
nodes_gdf.plot(
    column="cc_restaurant_400_wt",
    cmap="magma",
    legend=False,
    ax=ax,
)
data_gdf[data_gdf["amenity"] == "restaurant"].plot(
    markersize=2,
    edgecolor=None,
    color="white",
    legend=False,
    ax=ax,
)
ax.axis(False)
(np.float64(697035.8123208123),
 np.float64(700647.6928482385),
 np.float64(5709134.052621752),
 np.float64(5712638.694212982))

fig, ax = plt.subplots(1, 1, figsize=(8, 8), facecolor="#1d1d1d")
nodes_gdf.plot(
    column="cc_pub_nearest_max_800",
    cmap="viridis_r",
    legend=False,
    ax=ax,
)
data_gdf[data_gdf["amenity"] == "pub"].plot(
    markersize=2,
    edgecolor=None,
    color="white",
    legend=False,
    ax=ax,
)
ax.axis(False)
(np.float64(697035.8123208123),
 np.float64(700647.6928482385),
 np.float64(5709196.42405734),
 np.float64(5712635.724144622))