The Problem
Urban analysts often have to convert raw geospatial layers (buildings, streets, GTFS feeds, OD matrices) into network structures before they can apply graph‑based algorithms or GNNs. Manual stitching of geopandas, networkx and torch‑geometric pipelines is error‑prone and hard to reproduce across projects.
What This Does
city2graph provides a library that bridges those ecosystems. Core converters live in city2graph/graph.py (gdf_to_nx, gdf_to_pyg), while domain‑specific helpers sit in city2graph/morphology.py, city2graph/proximity.py, city2graph/transportation.py and city2graph/mobility.py. The public API is exposed through city2graph/__init__.py, which re‑exports the most‑used functions. Documentation (the 48 files under docs/) describes each module and includes Jupyter notebooks that demonstrate typical workflows (e.g., docs/examples/generating_graphs_by_proximity.ipynb).
How It Is Wired
Execution starts in the test suite; the only explicit entry point is the execute function in tests/test_transportation.py:339. Real‑world usage would call the same converters directly.
- Control flow hot‑spots – The internal call graph shows that
gdf_to_nx(incity2graph/utils.py) is invoked from 44 locations,create_isochrone(incity2graph/morphology.py) from 43, anddict_to_con(incity2graph/transportation.py) from 39. These functions are therefore the highest‑blast‑radius nodes; any change ripples through many modules and tests.
- Module responsibilities –
city2graph/utils.py(111 functions, 2 classes) handles generic conversions and validation; it is called from 10 other files.city2graph/graph.py(38 functions, 3 classes) implements theGraphclass and thegdf_to_pyg/pyg_to_gdfpipelines.city2graph/proximity.py(39 functions, 3 classes) computes spatial‑weight matrices and validates proximity inputs.city2graph/morphology.py,city2graph/transportation.py,city2graph/mobility.pyeach provide domain‑specific builders that ultimately call the generic utils.
- External interactions – Only two functions perform I/O: one reads GeoJSON files (
tests/data/*.geojson) and another writes temporary files during test fixtures. A single function launches an external command viasubprocess(used in the GTFS loader). No database connections are present.
- Circularity – The import graph contains 25 internal modules with zero circular dependencies, so the dependency chain is acyclic and relatively safe to refactor.
How To Use It
# Clone the repo
git clone https://github.com/moses-y/city2graph
cd city2graph
# Install with uv (lockfile present) or pip
uv pip install -e . # editable install, respects pyproject.toml
# or
pip install -e .
The library can also be run inside the provided Docker image:
docker build -t city2graph .
docker run --rm -v $(pwd):/app city2graph python -c \
"import city2graph as c2g; g = c2g.graph_from_geojson('tests/data/sample_segments.geojson')"
No environment variables are required; the only configuration is the optional pyproject.toml extras (cpu, cu126, etc.) for PyTorch support.
Real‑World Use
A data‑science team can load a city’s building footprint, convert it to a NetworkX graph, enrich it with a proximity matrix, then feed the result to a GNN:
import city2graph as c2g
buildings = c2g.read_geojson("city_buildings.geojson")
nx_graph = c2g.gdf_to_nx(buildings)
prox_matrix = c2g.proximity_matrix(nx_graph, radius=200)
pyg_data = c2g.gdf_to_pyg(buildings, edge_weight=prox_matrix)
# pass `pyg_data` to a torch_geometric model
Code Health & Issues
- HIGH – Pin GitHub Actions to commit SHA (
.github/workflows/*.yaml). - HIGH – Oversized files (
city2graph/utils.py,graph.py,proximity.py– >4 k lines each). - HIGH – Deep nesting (max indent depth = 6) in
utils.py,morphology.py,transportation.py. - HIGH – Duplicated code blocks across
base.py,utils.py,data.py, tests. - MEDIUM – Base Docker image uses mutable tag
jupyter/datascience-notebook:latest. - MEDIUM – No dependency‑vulnerability scan in CI.
- MEDIUM – Large binary assets (
docs/assets/logos/city2graph.psd, several.ipynband.mp4files) inflate the repo; should be moved to LFS or external storage. - LOW – Workflow jobs lack
timeout-minuteslimits.
No missing license, CI is present, and a lockfile (uv.lock) mitigates reproducibility for Python packages.
The Bottom Line
city2graph delivers a functional bridge between geospatial data frames and graph‑oriented ML tools, with a well‑documented public API. However, the core utility modules are large and deeply nested, making maintenance risky; refactoring into smaller, single‑responsibility files would lower cognitive load. It is suitable for teams that need a ready‑made conversion layer but should allocate effort to improve code modularity and tighten CI security.