Skip to content

Repository files navigation

network-centrality

network-centrality is a GeoDataFrame- and OSMnx-first Python package for calculating standard and origin-destination-weighted centrality on spatial transportation networks. It supports OSMnx graphs, user-prepared node and edge layers, optional GeoPackage or PostGIS I/O, configurable routing costs, approach-connector network splitting, hard crossing constraints, soft routing penalties, optional Level of Traffic Stress logic, and aggregation of split-edge results back to their original parent links. The package is useful for identifying important network links under distance-, time-, demand-, stress-, or policy-constrained routing scenarios.

Copyright © 2026 Brian Almdale

This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

Package status

The package begins at version 0.1.0 and is under active development. An open-source license has not yet been selected, so no license file is included in this initial package structure.

Installation

Conda development environment

Create the provided conda environment, activate it, and install the package in editable mode:

conda env create -f environment.yml
conda activate network-centrality
python -m pip install -e .

The conda environment includes the optional PostGIS dependencies and development tools.

Pip editable installation

From an existing Python 3.11+ environment:

python -m pip install -e .

Install PostGIS support and development tools when needed:

python -m pip install -e ".[postgis,dev]"

Quick start

The following matches the main workflow in examples/example_usage.py:

import osmnx as ox

from network_centrality import (
    add_osmnx_travel_time,
    load_osm_graph_from_place,
    run_centrality,
    write_centrality_outputs,
)

G = load_osm_graph_from_place(
    place="Seattle, Washington, USA",
    network_type="drive",
    exclude_highway_tags={"motorway", "motorway_link", "service"},
)
G = ox.project_graph(G)
G = add_osmnx_travel_time(G, fallback_speed_kph=40.0)

nodes, edges = run_centrality(
    G=G,
    cost_col="travel_time",
    approx_k=1_000,
    run_standard_metrics=True,
)

write_centrality_outputs(
    nodes=nodes,
    edges=edges,
    out_gpkg="outputs/network_centrality.gpkg",
    dry_run=False,
)

Run the complete parameterized example without writing files:

python examples/example_usage.py

Add --write to create the output GeoPackage. Use python examples/example_usage.py --help to review the location, OD-input, cost-cutoff, and output options.

Public API

Configuration

  • ODWeightSpec: Defines origin and destination node-weight columns for implicit all-to-all OD centrality.
  • MatrixODSpec: Defines the origin, destination, demand, and output columns used by an explicit OD matrix.
  • DEFAULT_SEGMENT_LTS_MULTIPLIERS: Default segment LTS routing-cost multipliers.
  • DEFAULT_CROSSING_LTS_PENALTIES: Default crossing LTS additive penalties.

Graph construction and preparation

  • load_osm_graph_from_place(): Downloads an OSMnx graph and optionally filters it by OSM highway tags.
  • filter_graph_edges_by_highway(): Filters an existing graph using included or excluded highway tags.
  • graph_from_user_gdfs(): Converts routing-ready node and edge GeoDataFrames into an OSMnx-compatible graph.
  • build_nodes_from_edge_endpoints(): Derives a node layer from edge start and end geometries.
  • make_edges_bidirectional(): Duplicates logically undirected edges in the reverse direction.
  • ensure_graph_edge_ids(): Preserves unique IDs and assigns stable {u}_{v}_{key} IDs to missing, null, or duplicate values.
  • collapse_to_min_cost_digraph(): Keeps the lowest-cost parallel edge for each directed node pair.
  • validate_positive_weight(): Validates that each graph edge has a positive routing cost.
  • make_approach_points_from_graph(): Creates setback approach points near both endpoints of each directed edge.

Routing costs

  • add_length_from_geometry(): Calculates metric edge length when the selected length field is incomplete.
  • add_travel_time_from_speed(): Calculates travel time in seconds from meters and miles per hour.
  • add_osmnx_travel_time(): Uses OSMnx speed and travel-time preparation helpers.

Approach constraints and stress networks

  • match_approach_points_to_edge_endpoints(): Matches approach points to edges and their nearest endpoint nodes.
  • add_unsignalized_arterial_crossing_filter(): Creates allowed and optional penalty fields for unsignalized arterial crossings.
  • build_approach_split_constraint_network(): Splits links at approach points and applies generic segment or connector constraints.
  • build_approach_split_stress_network(): Provides the backward-compatible LTS wrapper around generic splitting.
  • make_allowed_subgraph(): Removes edges that fail a selected boolean constraint field.
  • make_low_stress_subgraph(): Provides the backward-compatible low-stress subgraph alias.

Centrality metrics

  • standard_edge_betweenness_df(): Calculates weighted edge betweenness on a directed graph.
  • standard_node_centrality_df(): Calculates node betweenness, closeness, degree, in-degree, and out-degree.
  • od_weighted_edge_centrality_from_node_weights(): Routes implicit OD demand based on origin-weight times destination-weight.
  • od_weighted_edge_centrality_from_matrix(): Routes demand from an explicit OD matrix.
  • add_percentile_columns(): Adds percentile ranks for selected result fields.
  • add_weighted_composite_score(): Combines selected metrics into a weighted score.
  • run_centrality(): Orchestrates graph preparation, standard metrics, OD metrics, percentiles, and composite scoring.

OD preparation

  • snap_points_to_graph_nodes(): Snaps point geometries to their nearest graph nodes.
  • make_node_weights_from_polygons(): Converts polygon attributes into aggregated graph-node weights.
  • make_block_group_od_matrix(): Materializes a polygon-to-polygon OD matrix using network shortest-path costs.

Result aggregation and comparison

  • aggregate_split_edges_to_parent(): Aggregates split-edge metrics to original parent-edge IDs.
  • compare_parent_scores(): Compares full and constrained scenario metrics at the parent-edge level.
  • compare_full_and_low_stress_parent_scores(): Provides the backward-compatible LTS comparison alias.

Input and output

  • read_gpkg_network(): Reads edge and optional node layers from a GeoPackage.
  • read_postgis_network(): Reads edge and optional node queries through a generic SQLAlchemy connection.
  • write_centrality_outputs(): Writes nodes and edges to a GeoPackage; defaults to dry_run=True.
  • write_outputs_to_postgis(): Writes nodes and edges to PostGIS; defaults to dry_run=True.

Routing-cost units

cost_col controls every shortest-path calculation. The units of max_cost or od_max_cost must match the selected field:

  • length: typically meters.
  • travel_time: seconds when generated by OSMnx or add_travel_time_from_speed().
  • routing_cost: user-defined units, often length-equivalent after penalties.
  • bike_stress_cost: length-equivalent units under the default LTS multiplier and penalty approach.

Known limitations and notes

  • collapse_to_min_cost_digraph() retains only the lowest-cost parallel edge for each directed u/v pair during centrality routing. Results are assigned to that retained edge ID; higher-cost parallel edges receive no routed score.
  • Approximate betweenness is controlled by approx_k. Passing None, or a value at least as large as the graph node count, requests exact NetworkX calculation and can become expensive on large networks.
  • Implicit node-weight OD centrality and explicit OD-matrix centrality perform repeated shortest-path routing. Runtime can grow rapidly with the number of origins, destinations, and reachable OD pairs.
  • make_block_group_od_matrix() materializes individual polygon-to-polygon pairs. For large datasets, make_node_weights_from_polygons() with implicit node-weight centrality is generally more memory efficient.
  • Approach splitting assumes LineString edge geometry and requires careful QA of duplicate or incorrectly matched approach points. Duplicate projected distances on the same edge retain the first matched point, matching the prototype behavior.
  • Approach-point creation and distance matching should use a projected CRS with meter units. make_approach_points_from_graph() explicitly requires a projected graph.
  • When a base cost is missing during approach splitting, the prototype falls back to geometry length in the layer CRS. This is only meaningful when that CRS uses appropriate distance units.
  • Standard centrality metrics do not use OD pairs. OD inputs affect only the OD-weighted edge-centrality fields and any composite score that includes those fields.
  • The package does not create or manage database credentials. PostGIS helpers accept a caller-provided SQLAlchemy engine or connection.
  • All write-capable public helpers default to dry_run=True; callers must explicitly set dry_run=False to modify a GeoPackage or database.

Prototype migration notes

The package preserves the reusable behavior of centrality_tool_generic_approach_constraints.py while removing executable testing blocks and hardcoded local paths. Two conflicting definitions of ensure_graph_edge_ids() were consolidated: unique existing IDs remain unchanged, while missing, null, and duplicate IDs receive stable IDs based on u, v, and key. Seattle-specific functions were not included in the importable package; their workflow informed the parameterized example script.

About

Python package for calculating standard and origin-destination-weighted centrality on spatial transportation networks.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages