Troubleshooting
Common problems, organised by symptom. Each entry gives the cause, the fix, and a link to the guide section that explains the underlying behaviour.
All my distances or results look wrong by orders of magnitude
Your data is in a geographic CRS, so coordinates are degrees rather than metres and every distance calculation is off by roughly five orders of magnitude. cityseer requires a projected CRS with metre units.
Check with gdf.crs (EPSG:4326 / WGS 84 is the usual culprit) or gdf.crs.is_projected, then reproject before passing data to cityseer:
gdf = gdf.to_crs(epsg=32630) # a UTM zone or regional projection with metre units
Search your city at epsg.io to find a suitable UTM zone, or use a regional projection such as EPSG:3035 (Europe), EPSG:27700 (Great Britain), or EPSG:2154 (France). When building from OSM with CityNetwork.from_osm and related io functions, cityseer selects an appropriate UTM zone automatically; the pitfall arises mainly when loading networks or data layers from files.
Values fade toward the edge of my study area
Nodes near the boundary can only “see” part of their real catchment, so their values roll off artificially. This is edge rolloff, explained at Edge Rolloff.
The fix is to buffer: download or build a network extending at least the maximum distance threshold beyond your study area (data layers should cover the buffered extent too), then restrict reporting to the study area itself. With CityNetwork, pass the unbuffered boundary to set_boundary; with the lower-level API, set buffer nodes to live=False:
cn.set_boundary(study_area_polygon) # buffer nodes participate in routing but are not reported
See the live nodes recipe for a worked example.
Computation is slow
Runtime scales with the number of edges, the number of distance thresholds, and the reachability at each threshold, so the largest threshold usually dominates. Three things to check:
- Distance thresholds. Each threshold adds cost, and long thresholds cost disproportionately more because far more of the network is reachable from each node. Drop thresholds you do not need.
- Decomposition. Decomposing a network multiplies its node count, and computation scales with it; a fine decomposition of a large extent can grow the network by an order of magnitude. Decompose district-scale extents rather than whole cities, and prefer coarser maxima. See Decomposition.
- Sampling. For large networks at long thresholds, pass
sample=Trueto the centrality methods. Adaptive sampling estimates results from a subset of source nodes with controlled error, and automatically falls back to exact computation where sampling would not pay. See Adaptive Sampling.
As a rough calibration: the algorithms are parallelised in Rust, and a district-scale run such as the quickstart, a 1km-radius OSM network with thresholds up to 2000m, completes in seconds on an ordinary laptop. If a run of that size takes many minutes, one of the points above is usually the reason.
OSM download fails or times out
CityNetwork.from_osm and io.osm_graph_from_poly fetch data from the Overpass API, a shared public service that rate-limits and sometimes rejects or delays requests. cityseer retries failed requests automatically (three attempts with a 300s timeout by default; both are configurable on the lower-level io functions), so transient failures often resolve themselves.
If requests are rejected outright, check your cityseer version: from v5, requests send an identifying User-Agent, which Overpass now requires; older versions receive 406 Not Acceptable errors. Upgrade to fix.
Above all, avoid re-downloading on every run. Fetch once, compute, and save; subsequent runs load from disk with no network access:
cn.save("my_network") # writes my_network.nodes.parquet + my_network.state.pkl
cn = CityNetwork.load("my_network")
My data points don’t appear in results
Data features are assigned to the nearest street edge before aggregation, and features further than the maximum assignment distance (default 100m) from any edge are silently excluded. Features outside the network’s extent entirely can never be reached and contribute nothing.
If legitimate points are being dropped, for example building centroids set back from the street, raise the assignment distance:
cn.compute_accessibilities(data_gdf=data_gdf, ..., max_netw_assign_dist=400)
Also confirm the data layer covers the buffered network extent (see Edge Rolloff) and shares the network’s projected CRS.
My accessibility values don’t reflect proximity
With CityNetwork, accessibility counts are plain (unweighted) by default: a shop 50m away counts the same as one 750m away. Pass a decay_fn expression so that nearer features contribute more; the lower-level layers functions instead default to emitting both variants, suffixed _nw (non-weighted) and _wt (decay-weighted). Use a decay for proximity-sensitive questions such as walkability; use a plain count when presence alone is the question, such as counting all parks within reach. See Decay Functions.
Unpacking errors after upgrading to v5.8
The data-layer methods (compute_accessibilities, compute_mixed_uses, compute_stats) no longer return the data GeoDataFrame as a second value; that was a legacy return, and the assignment information now lives internally. Code written against earlier versions that unpacks two values fails in one of two ways:
- With
CityNetwork,cn, data = cn.compute_...(...)raises aTypeErrorexplaining the change directly. - With the functional
layersAPI,nodes_gdf, data_gdf = layers.compute_...(...)raisesValueError: too many values to unpack (expected 2), because the single returnedGeoDataFrameis being iterated.
The fix is the same in both cases: assign the single return value.
cn = cn.compute_accessibilities(...) # CityNetwork: returns self, chainable
nodes_gdf = layers.compute_stats(...) # functional API: returns nodes_gdf
Angular centrality raises an error
Simplest-path (angular) analysis requires a dual graph, where street segments are nodes and turning angles can be tracked between them. Calling an angular function on a primal graph raises an error stating that a dual graph is required.
CityNetwork builds the dual graph automatically, so this error only arises with the lower-level API: convert the primal graph before ingestion:
G_dual = graphs.nx_to_dual(G) # then network_structure_from_nx(G_dual, ...)
See Primal and dual graphs and the dual graph recipe.