cityImage is a Python package for analysing urban legibility and extracting a computational version of Kevin Lynch's Image of the City from geospatial data.
The package works with user-provided GeoPandas datasets and with OpenStreetMap data acquired through OSMnx. The refactored API keeps cityImage-specific schema, topology, barrier, district, landmark, and scoring semantics while delegating generic acquisition, spatial joins, plotting, graph algorithms, and raster operations to established libraries.
For full documentation and examples, see the cityImage documentation.
cityImage operationalises Lynchian urban elements as reproducible geospatial workflows:
- Paths and nodes from street-network structure, centrality, graph topology, and optional angular/dual-graph semantics.
- Edges from structuring barriers such as water, railways, large parks, and major roads.
- Districts from network partitions, polygonisation, and gateway detection.
- Landmarks from structural, visual, cultural, and pragmatic salience.
- Imageability scores from composable landmark and urban-element indicators.
The package is designed as a semantic layer over geospatial data rather than as a replacement for general-purpose GIS, graph, or OSM tooling.
cityImage deliberately delegates generic operations to specialised libraries:
- GeoPandas/Shapely handle geometry containers, CRS conversion, spatial predicates, overlay-like operations, and file IO.
- OSMnx handles OpenStreetMap acquisition and raw OSM graph/feature access.
- NetworkX/iGraph handle graph representation and graph algorithms.
- python-louvain handles modularity-based community detection.
- Rasterio/rasterstats handle raster sampling and zonal statistics.
- Dask parallelises batching in the optional 3D sight-line workflow.
The specificity of cityImage is different: it defines the urban-image semantics that sit on top of those tools. In practice this means cityImage focuses on:
- stable nodes/edges/buildings schemas used across the package;
- conversion of raw files or OSM outputs into those schemas;
- graph cleaning and topology repair where the cityImage node/edge relationship must be preserved;
- dual/primal graph transformations with street-segment angle semantics;
- barrier extraction and assignment as Lynchian edge semantics;
- district and gateway logic from network partitions;
- landmark/imageability component scores and final score composition;
- example-ready workflows that keep the same conceptual outputs across cities.
So, for example, OSMnx can acquire a walk network, GeoPandas can spatially join layers, and NetworkX can compute graph measures. cityImage connects those pieces into a reproducible workflow that returns paths, nodes, edges, districts, landmarks, and imageability scores in a consistent schema.
The methods are based on:
Filomena, G., Verstegen, J. A., & Manley, E. (2019). A computational approach to The Image of the City. Cities, 89, 14–25.
Core install:
pip install cityImageThe core install includes graph centrality (igraph) and region detection (python-louvain), and keeps only heavier, workflow-specific dependencies out of the base environment. Use extras for those:
pip install "cityImage[plot]" # plotting helpers (matplotlib, mapclassify)
pip install "cityImage[height]" # DEM/DTM height helpers
pip install "cityImage[visibility3d]" # 3D sight-line workflow (adds dask + psutil)
pip install "cityImage[all]" # all optional runtime dependenciesFor development:
pip install -e ".[all,test,docs,dev]"The current API separates cityImage-owned semantics from external libraries. Submodules marked (extra: X) need the corresponding optional install (see Installation); the rest are available with the core install.
cityImage.schemaandcityImage.adapters: stable node/edge/building schemas, validation, and input standardisation.cityImage.io: file/GeoPandas loading into cityImage schemas.cityImage.osm: OSMnx acquisition into cityImage schemas.cityImage.pedestrian: pedestrian-network filtering and construction from OSM highway features.cityImage.networkandcityImage.network_topology: street-network construction, cleaning, simplification, and topology repair. See the network cleaning guide for every case, with diagrams.cityImage.graphandcityImage.angles: primal/dual graph semantics and angular relationships. Each dual edge records whether the move between two segments is allowed both ways or one way (oneway), anddual_graph_fromGDFkeeps it, with the edge'suandv, on each edge.cityImage.centrality: node/edge centrality wrappers (iGraph-based measures; iGraph is a core dependency).append_edges_metricstakes edge measures computed on aGraph(graph_fromGDF) or aMultiGraph(multiGraph_fromGDF).cityImage.barriers: natural and artificial barriers such as rivers, railways, parks, and major roads.cityImage.regions: districts and gateways from network partitions (modularity-based detection via python-louvain, a core dependency).cityImage.landuse: land-use derivation, classification, sparse representation, and assignment.cityImage.buildings: building selection, study-area and height-reading helpers.cityImage.height: DEM/DTM-based building heights. (extra: height)cityImage.landmarksandcityImage.scoring: Lynchian landmark and imageability scoring. In a layer with heights, a building without one scores 0 on the visual component.cityImage.visibility2d: 2D visibility workflows.cityImage.visibility3d: optional 3D sight-line workflows;verbose=Truelogs progress throughlogging. (extra: visibility3d)cityImage.plotting: optional static plotting helpers. (extra: plot)
import cityImage as ci
nodes, edges = ci.network_from_osm(
"Susa, Italy",
download_method="OSMplace",
network_type="walk",
crs="EPSG:32632",
)
buildings = ci.buildings_from_osm(
"Susa, Italy",
download_method="OSMplace",
crs="EPSG:32632",
)
barriers = ci.barriers_from_osm(
"Susa, Italy",
download_method="OSMplace",
crs="EPSG:32632",
)OSM height tags cover only some buildings, so buildings_from_osm leaves them
out unless keep_osm_heights=True. buildings_from_file reads heights from
height_field and drops buildings lower than min_height; a building without a
height (missing, unreadable or not above zero) is kept with a NaN height. Heights
are yours to supply: in the scores a building without one has no visual score
(it adds nothing to the landmark score), and it is neither a target nor an
obstruction in the 3D sight lines.
ruff check cityImage tests scripts
ruff format --check cityImage tests scripts
pytest -m "not network" -ra
python -m build
twine check dist/*See CHANGELOG.md.
cityImage is distributed under the GNU General Public License v3.0 or later.
