network

High-level CityNetwork API for urban network analysis. The CityNetwork class wraps network construction, centrality computation, land-use analysis, and export into a single interface. It builds dual graphs (where street segments become nodes rather than intersections) directly from LineString geometries, enabling both shortest-path (metric distance) and simplest-path (angular change) analysis. See the Guide for concepts, conventions, and worked examples.

CityNetwork

High-level interface for urban network analysis. Wraps network construction, centrality computation, and land-use analysis into a single object that manages graph topology, node attributes, and coordinate reference systems. The network is built as a dual graph where street segments become nodes and intersections become edges, enabling both shortest-path (metric) and simplest-path (angular) centrality analysis.

Construct instances via the class methods rather than calling __init__ directly:

  • from_geopandas – from a GeoDataFrame of LineString geometries
  • from_wkts – from a dictionary of WKT strings or Shapely geometries
  • from_nx – from a cityseer-compatible NetworkX MultiGraph
  • from_osm – from OpenStreetMap via a bounding polygon
  • load – from a previously saved parquet/pickle pair

Most methods return self to support method chaining:

cn = (
    CityNetwork.from_geopandas(edges_gdf, crs=32632)
    .set_boundary(boundary_polygon)
    .centrality_shortest(distances=[500, 1000, 2000])
)

The underlying graph construction automatically cleans input geometries by removing short self-loops, near-duplicate edges, and short danglers. Use the feature_status property to inspect which input features were filtered and why.

Dual graph architecture

CityNetwork always constructs a dual graph internally. In the dual representation, each street segment becomes a node (positioned at the segment midpoint) and edges connect segments that share a common intersection. This enables both shortest-path and simplest-path (angular) analysis from a single topology:

  • Shortest-path analysis uses metric distances along street segments.
  • Simplest-path analysis uses cumulative angular change along streets and at intersections as the routing cost.

Because the dual is built automatically, there is no need to call nx_to_dual when using CityNetwork. Although the topology is dual internally, results are visualised and exported as the original street segment geometries via to_geopandas, so each row in the output corresponds to one input street.

Working with results

All computed metrics are written to the internal nodes_gdf GeoDataFrame. Since CityNetwork uses a dual graph, each row in nodes_gdf represents a street segment, with a Point geometry at the segment midpoint.

To retrieve results with the original LineString geometries, use to_geopandas:

cn = CityNetwork.from_geopandas(edges_gdf, crs=32632)
cn.centrality_shortest(distances=[800])

# Midpoint geometries (internal representation)
cn.nodes_gdf["cc_harmonic_800"]

# Original LineString geometries with the same computed columns
result_gdf = cn.to_geopandas()
result_gdf["cc_harmonic_800"]

Column names follow the cc_{metric}_{distance} convention described in the Column Naming Conventions section.

Feature cleaning

Input geometries are automatically cleaned during construction. Short self-loops, near-duplicate edges, and short danglers are removed. The feature_status property returns a Series indicating the status of each input feature:

cn = CityNetwork.from_geopandas(edges_gdf, crs=32632)
print(cn.feature_status.value_counts())
# active              142
# short_dangler         3
# duplicate             1

Saving and loading

Networks can be serialised to disk and restored later, preserving all computed metrics:

cn.save("my_network")
# Creates: my_network.nodes.parquet, my_network.state.pkl

cn_restored = CityNetwork.load("my_network")

Incremental updates

The update method performs an incremental topology diff: unchanged features keep their node indices, added features are inserted, and removed features are deleted. Previously computed centrality columns are cleared since they are invalidated by topology changes.

# Initial build
cn = CityNetwork.from_geopandas(edges_gdf, crs=32632)
cn.centrality_shortest(distances=[800])

# Update with modified geometries
cn.update(updated_edges_gdf)
cn.centrality_shortest(distances=[800])

Typical workflow

import geopandas as gpd
from cityseer.network import CityNetwork
from cityseer import decay

# 1. Load street network edges
edges_gdf = gpd.read_file("streets.gpkg")

# 2. Build the network
cn = CityNetwork.from_geopandas(edges_gdf, crs="EPSG:32632")

# 3. Compute centrality at multiple scales
cn.centrality_shortest(distances=[400, 800, 1600])
cn.centrality_simplest(distances=[400, 800, 1600])

# 4. Compute land-use accessibility
landuses_gdf = gpd.read_file("landuses.gpkg")
cn, landuses_gdf = cn.compute_accessibilities(
    data_gdf=landuses_gdf,
    landuse_column_label="category",
    accessibility_keys=["retail", "park"],
    distances=[400, 800],
    decay_fn=decay.exponential(),
)

# 5. Compute statistical aggregations
prices_gdf = gpd.read_file("property_prices.gpkg")
cn, prices_gdf = cn.compute_stats(
    data_gdf=prices_gdf,
    stats_column_labels=["price"],
    distances=[800, 1600],
    decay_fn=decay.gaussian(peak=400, cutoff=1600),
)

# 6. Export results with original LineString geometries
result_gdf = cn.to_geopandas()
result_gdf.to_file("results.gpkg")

For end-to-end worked examples with real-world data, see the Cityseer Examples site, including the Quickstart notebook.

CityNetwork

CityNetwork
(
network_structure : NetworkStructure
nodes_gdf : geopandas.geodataframe.GeoDataFrame
*
_state
_crs
)

network_structure: NetworkStructure

nodes_gdf: geopandas.geodataframe.GeoDataFrame

to_geopandas

to_geopandas
(
self
)->[ GeoDataFrame ]

Return a GeoDataFrame with the original input LineString geometries. The returned GeoDataFrame contains all computed columns (centrality metrics, layer results, etc.) joined to the original edge geometries rather than the midpoint representations used internally.

Returns

gdf
GeoDataFrame

A new GeoDataFrame indexed by feature id with LineString geometries.

Notes

cn.centrality_shortest(distances=[800])
result_gdf = cn.to_geopandas()

# result_gdf has LineString geometries (not midpoint Points)
print(result_gdf.geometry.geom_type.unique())  # ['LineString']

# All computed columns are present
print(result_gdf["cc_harmonic_800"])

# Export to file
result_gdf.to_file("centrality_results.gpkg")

is_dual: bool

is_directed: bool

crs: pyproj.crs.crs.CRS | None

node_count: int

feature_status: pandas.Series

from_wkts

@classmethod
from_wkts
(
cls
wkts
*
crs
boundary : shapely.geometry.base.BaseGeometry | None = None
directed : bool = False
oneway_fids
impedances
)->[ CityNetwork ]

Construct a CityNetwork from a dictionary of WKT strings or Shapely geometries.

Parameters

wkts
dict[Any, str] | dict[Any, BaseGeometry]

A mapping from feature identifiers to WKT strings or Shapely LineString geometries. Input geometries may include z (elevation) coordinates, which are preserved and used for slope-based walking impedance calculations.

crs
Any

A projected coordinate reference system (EPSG code, CRS object, or proj string).

boundary
BaseGeometry

Optional polygon in the same projected CRS; nodes inside are marked as live, nodes outside as dead.

directed
bool

If True, build a directed network. Requires oneway_fids. Features in oneway_fids are one-way (in LineString coordinate order); all other features are bidirectional.

oneway_fids
set[Any] | None

Feature IDs that are one-way when directed=True. Ignored if directed=False.

impedances
dict[Any, float] | None

Optional mapping from primal feature ID to its impedance factor. Each dual edge’s imp_factor becomes the length-weighted mean of its two adjacent primal segments’ impedances; missing entries default to 1.0. See the Edge Impedance section of the guide.

Returns

network
CityNetwork

A new CityNetwork instance.

Raises

ValueError

If directed=True but oneway_fids is not provided.

Notes

from shapely.geometry import LineString
from cityseer.network import CityNetwork

wkts = {
    "street_a": LineString([(0, 0), (100, 0)]),
    "street_b": LineString([(100, 0), (100, 100)]),
    "street_c": LineString([(100, 0), (200, 0)]),
}
cn = CityNetwork.from_wkts(wkts, crs=32632)
cn.centrality_shortest(distances=[200])

from_geopandas

@classmethod
from_geopandas
(
cls
gdf : geopandas.geodataframe.GeoDataFrame
*
crs
boundary : shapely.geometry.base.BaseGeometry | None = None
directed : bool = False
)->[ CityNetwork ]

Construct a CityNetwork from a GeoDataFrame of LineString geometries. Extra columns from the input GeoDataFrame are carried through to the internal nodes GeoDataFrame. The CRS is read from the GeoDataFrame unless explicitly overridden.

Parameters

gdf
GeoDataFrame

A GeoDataFrame with LineString or MultiLineString geometries. The index must be unique. Input geometries may include z (elevation) coordinates, which are preserved and used for slope-based walking impedance calculations.

crs
Any

Optional projected CRS override. If None, uses the GeoDataFrame’s CRS.

boundary
BaseGeometry

Optional polygon in the same projected CRS; nodes inside are marked as live, nodes outside as dead.

directed
bool

If True, build a directed network. Requires a boolean oneway column in the GeoDataFrame. Features with oneway=True are one-way in LineString coordinate order; features with oneway=False are bidirectional.

Returns

network
CityNetwork

A new CityNetwork instance.

Raises

ValueError

If directed=True but the GeoDataFrame has no oneway column.

Notes

An optional imp_factor column on the input GeoDataFrame is propagated to each dual edge as the length-weighted mean of the two adjacent primal segments’ impedances; omit it to leave every dual edge at the default 1.0. See the Edge Impedance section of the guide.

import geopandas as gpd
from shapely.geometry import LineString
from cityseer.network import CityNetwork

gdf = gpd.GeoDataFrame(
    {
        "geometry": [
            LineString([(0, 0), (100, 0)]),
            LineString([(100, 0), (100, 100)]),
            LineString([(100, 0), (200, 0)]),
        ]
    },
    crs="EPSG:32632",
)
cn = CityNetwork.from_geopandas(gdf)
cn.centrality_shortest(distances=[200])
print(cn.nodes_gdf[["cc_harmonic_200", "cc_betweenness_200"]])

With directed one-way streets:

gdf = gpd.GeoDataFrame(
    {
        "geometry": [
            LineString([(0, 0), (100, 0)]),
            LineString([(100, 0), (200, 0)]),
        ],
        "oneway": [True, False],
    },
    crs="EPSG:32632",
)
cn = CityNetwork.from_geopandas(gdf, directed=True)

from_nx

@classmethod
from_nx
(
cls
graph : networkx.classes.multigraph.MultiGraph
*
boundary : shapely.geometry.base.BaseGeometry | None = None
)->[ CityNetwork ]

Construct a CityNetwork from a cityseer-compatible NetworkX graph. The input graph must be a primal edge graph (not a dual graph) with geom attributes on edges and a crs attribute on the graph. Node live attributes are preserved.

When a MultiDiGraph is passed, directed mode is enabled automatically: each directed edge becomes its own one-way dual node (in the coordinate order of the directed edge). Two-way streets should be represented as two reciprocal edges (A to B and B to A), which produce two separate dual nodes.

Parameters

graph
nx.MultiGraph | nx.MultiDiGraph

A cityseer-compatible primal NetworkX graph. MultiDiGraph enables directed routing. Any imp_factor edge attribute is propagated to each dual edge as the length-weighted mean of the two adjacent primal segments’ impedances (default 1.0 if absent). See the Edge Impedance section of the guide.

boundary
BaseGeometry

Optional polygon in the same projected CRS; nodes inside are marked as live, nodes outside as dead.

Returns

network
CityNetwork

A new CityNetwork instance.

Raises

ValueError

If the input graph is a dual graph.

Notes

From an undirected graph:

import networkx as nx
from shapely.geometry import LineString
from cityseer.network import CityNetwork

G = nx.MultiGraph(crs="EPSG:32632")
G.add_node("a", x=0.0, y=0.0)
G.add_node("b", x=100.0, y=0.0)
G.add_node("c", x=200.0, y=0.0)
G.add_edge("a", "b", geom=LineString([(0, 0), (100, 0)]))
G.add_edge("b", "c", geom=LineString([(100, 0), (200, 0)]))

cn = CityNetwork.from_nx(G)

From a directed MultiDiGraph (e.g. via OSMnx):

G = nx.MultiDiGraph(crs="EPSG:32632")
G.add_node("a", x=0.0, y=0.0)
G.add_node("b", x=100.0, y=0.0)
# One-way: a -> b only
G.add_edge("a", "b", key=0, geom=LineString([(0, 0), (100, 0)]))
cn = CityNetwork.from_nx(G)
assert cn.is_directed

from_osm

@classmethod
from_osm
(
cls
poly_geom : shapely.geometry.base.BaseGeometry
*
poly_crs_code : int = 4326
to_crs_code : int | None = None
simplify : bool = True
boundary : shapely.geometry.base.BaseGeometry | None = None
**kwargs
)->[ CityNetwork ]

Construct a CityNetwork from OpenStreetMap data within a bounding polygon. Downloads the road network and converts it to a dual CityNetwork.

For directed (one-way) routing with OSM data, fetch a directed graph via OSMnx <https://osmnx.readthedocs.io/>_ and pass it to :meth:from_nx or convert it with :func:io.nx_from_osm_nx(directed=True) <cityseer.tools.io.nx_from_osm_nx>.

Parameters

poly_geom
BaseGeometry

A Shapely polygon defining the area of interest.

poly_crs_code
int

EPSG code for poly_geom. Defaults to 4326 (WGS84).

to_crs_code
int

Target projected EPSG code. If None, an appropriate UTM zone is inferred.

simplify
bool

Whether to simplify the OSM graph topology. Defaults to True.

boundary
BaseGeometry

Optional polygon for live/dead node assignment (in the target projected CRS).

**kwargs
None

Additional keyword arguments passed to io.osm_graph_from_poly.

Returns

network
CityNetwork

A new CityNetwork instance.

Notes

from shapely.geometry import box
from cityseer.network import CityNetwork

# Bounding box in WGS84 (lon/lat)
polygon = box(-0.13, 51.51, -0.12, 51.52)
cn = CityNetwork.from_osm(polygon, to_crs_code=32630)
cn.centrality_shortest(distances=[400, 800])

update

update
(
self
data
)->[ CityNetwork ]

Update the network topology with new or modified geometries. Performs an incremental diff against the current state: unchanged features retain their node indices, added features are inserted, and removed features are deleted. Previously computed centrality columns are cleared since they are invalidated by topology changes.

For directed networks built via from_geopandas(directed=True), the incoming GeoDataFrame must include the oneway column. Direction changes (even without geometry changes) trigger a rebuild.

Parameters

data
dict[Any, str] | dict[Any, BaseGeometry] | GeoDataFrame

The complete updated set of geometries (not just the diff).

Returns

self
CityNetwork

Returns self for method chaining.

set_boundary

set_boundary
(
self
polygon : shapely.geometry.base.BaseGeometry
)->[ CityNetwork ]

Set live/dead node status based on a boundary polygon. Nodes whose midpoints fall inside the polygon are marked live; others are marked dead. Dead nodes participate in traversal but their own values are not reported. Closeness skips dead sources in exact mode (a cost saving); betweenness sources from every node so that routes through the study area — including those between dead nodes — credit the live nodes they traverse.

Parameters

polygon
BaseGeometry

A Shapely polygon in the same projected CRS as the network.

Returns

self
CityNetwork

Returns self for method chaining.

set_all_live

set_all_live
(
self
)->[ CityNetwork ]

Mark all nodes as live, clearing any boundary restriction.

Returns

self
CityNetwork

Returns self for method chaining.

save

save
(
self
path : str | pathlib._local.Path
)

Save the network to disk as a parquet/pickle pair. Creates two files: <path>.nodes.parquet (the nodes GeoDataFrame with all computed columns) and <path>.state.pkl (source WKTs, boundary, and feature status). Use load to restore.

Parameters

path
str | Path

Base file path. File extensions are replaced automatically.

Notes

cn.centrality_shortest(distances=[800])
cn.save("my_network")
# Creates: my_network.nodes.parquet, my_network.state.pkl

# Later, restore the full network with all metrics
cn_restored = CityNetwork.load("my_network")
print(cn_restored.nodes_gdf["cc_harmonic_800"])

load

@classmethod
load
(
cls
path : str | pathlib._local.Path
)->[ CityNetwork ]

Load a previously saved CityNetwork from disk. Rebuilds the full graph topology from the saved source WKTs and merges any previously computed columns (centrality metrics, layer results) from the saved nodes GeoDataFrame.

Parameters

path
str | Path

Base file path (same as was passed to save).

Returns

network
CityNetwork

The restored CityNetwork instance.

centrality_shortest

centrality_shortest
(
self
**kwargs
)->[ CityNetwork ]

Compute shortest-path (metric) centrality. Wraps centrality_shortest. All keyword arguments are forwarded; see that function for the full parameter list including distances, minutes, closeness, betweenness, cycles, postprocess, segment_weighted, sample, and epsilon.

Returns

self
CityNetwork

Returns self for method chaining. Results are written to nodes_gdf.

Notes

# Multiple distance thresholds
cn.centrality_shortest(distances=[400, 800, 1600])

# With segment-length weighting
cn.centrality_shortest(distances=[400, 800], segment_weighted=True)

# With sampling for large networks
cn.centrality_shortest(
    distances=[800, 2000, 5000],
    sample=True,
    epsilon=0.05,
)

# Custom closeness metric
cn.centrality_shortest(
    distances=[800],
    closeness={"harmonic": "1/c", "gravity": "exp(-0.005 * c)"},
    betweenness={},
)

By default this emits just cc_harmonic_{d} (closeness) and cc_betweenness_{d}, with cycles off. Pass closeness / betweenness expression dicts (and cycles=True) to compute any of the metrics below (see Column Naming Conventions):

ColumnDescription
cc_density_{d}Count of reachable nodes (or total reachable street length if segment_weighted).
cc_harmonic_{d}Harmonic closeness: sum of inverse distances to reachable nodes (default).
cc_farness_{d}Sum of distances to reachable nodes.
cc_hillier_{d}Hillier normalisation (density² / farness).
cc_cycles_{d}Circuit rank: count of independent loops in the reachable subgraph.
cc_decay_{d}Decay-weighted closeness (e.g. exp(-4 * p)).
cc_betweenness_{d}Betweenness: count of shortest paths passing through each node (default).
cc_betweenness_decay_{d}Decay-weighted betweenness (e.g. exp(-4 * p)).

centrality_simplest

centrality_simplest
(
self
**kwargs
)->[ CityNetwork ]

Compute simplest-path (angular) centrality. Wraps centrality_simplest. All keyword arguments are forwarded; see that function for the full parameter list.

Returns

self
CityNetwork

Returns self for method chaining. Results are written to nodes_gdf.

Notes

cn.centrality_simplest(distances=[400, 800, 1600])

By default this emits just cc_harmonic_{d}_ang (closeness) and cc_betweenness_{d}_ang. Pass closeness / betweenness expression dicts to compute any of the metrics below (note the _ang suffix):

ColumnDescription
cc_density_{d}_angCount of reachable nodes (or total reachable street length if segment_weighted).
cc_harmonic_{d}_angHarmonic closeness using angular cost as impedance (default).
cc_farness_{d}_angSum of angular costs to reachable nodes.
cc_hillier_{d}_angHillier normalisation (density² / farness).
cc_betweenness_{d}_angBetweenness via simplest angular paths (angular choice; default).

build_od_matrix

build_od_matrix
(
self
od_df : pandas.DataFrame
zones_gdf : geopandas.geodataframe.GeoDataFrame
**kwargs
)->[ OdMatrix ]

Build an origin-destination (OD) matrix for OD-weighted betweenness. In standard betweenness, every pair of nodes contributes equally. OD-weighted betweenness instead weights each pair by observed trip counts between their respective zones (e.g. commuter cycling counts), so streets carrying more actual traffic receive higher scores.

Wraps build_od_matrix. See that function for the full parameter list.

Parameters

od_df
pd.DataFrame

Origin-destination flow data with columns for origin zone, destination zone, and trip weight.

zones_gdf
GeoDataFrame

Zone boundaries (polygons) or centroids (points) corresponding to the OD matrix.

Returns

od_matrix
OdMatrix

An OD matrix for use with betweenness_od.

betweenness_od

betweenness_od
(
self
od_matrix : OdMatrix
**kwargs
)->[ CityNetwork ]

Compute OD-weighted betweenness centrality. Weights betweenness by actual origin-destination trip counts rather than treating all paths equally. Only source nodes with outbound trips are traversed, and each shortest-path contribution is scaled by the corresponding OD weight.

Wraps betweenness_od. See that function for the full parameter list.

Parameters

od_matrix
OdMatrix

An OD matrix from build_od_matrix.

Returns

self
CityNetwork

Returns self for method chaining. Results are written to nodes_gdf.

compute_accessibilities

compute_accessibilities
(
self
data_gdf : geopandas.geodataframe.GeoDataFrame
**kwargs
)->[ CityNetwork GeoDataFrame ]

Compute land-use accessibility metrics. Counts how many instances of each specified land-use category (e.g. retail, parks) are reachable within each distance threshold, and records the nearest distance to each category.

Wraps compute_accessibilities. All additional keyword arguments are forwarded; see that function for the full parameter list including landuse_column_label, accessibility_keys, distances, minutes, decay_fn, and angular. decay_fn accepts a single expression or a {label: expression} dict that computes several decay variants in one network traversal, each label suffixing its output columns.

Parameters

data_gdf
GeoDataFrame

A GeoDataFrame of land-use points with categorical columns.

Returns

self
CityNetwork

Returns self with accessibility columns added to nodes_gdf.

data_gdf
GeoDataFrame

The input data GeoDataFrame with nearest network assignments.

Notes

from cityseer import decay

cn, landuses_gdf = cn.compute_accessibilities(
    data_gdf=landuses_gdf,
    landuse_column_label="category",
    accessibility_keys=["retail", "cafe", "park"],
    distances=[400, 800],
    decay_fn=decay.exponential(),
)
# Count of reachable "retail" within 800m
print(cn.nodes_gdf["cc_retail_800"])
# Nearest distance to "park" at the maximum threshold
print(cn.nodes_gdf["cc_park_nearest_max_800"])

compute_mixed_uses

compute_mixed_uses
(
self
data_gdf : geopandas.geodataframe.GeoDataFrame
**kwargs
)->[ CityNetwork GeoDataFrame ]

Compute mixed-use diversity metrics. Measures the diversity of land-use categories reachable from each node using Hill numbers: q=0 counts how many distinct types are present, q=1 measures diversity accounting for how evenly types are represented, and q=2 gives greater weight to the most common types.

Wraps compute_mixed_uses. All additional keyword arguments are forwarded; see that function for the full parameter list including landuse_column_label, distances, minutes, compute_hill, compute_shannon, compute_gini, decay_fn, and angular. decay_fn accepts a single expression or a {label: expression} dict that computes several decay variants in one network traversal, each label suffixing its output columns (only the Hill measures vary with decay).

Parameters

data_gdf
GeoDataFrame

A GeoDataFrame of land-use points with categorical columns.

Returns

self
CityNetwork

Returns self with mixed-use columns added to nodes_gdf.

data_gdf
GeoDataFrame

The input data GeoDataFrame with nearest network assignments.

Notes

cn, landuses_gdf = cn.compute_mixed_uses(
    data_gdf=landuses_gdf,
    landuse_column_label="category",
    distances=[400, 800],
)
# Hill q=0 (count of distinct land-use types) at 800m
print(cn.nodes_gdf["cc_hill_q0_800"])

compute_stats

compute_stats
(
self
data_gdf : geopandas.geodataframe.GeoDataFrame
**kwargs
)->[ CityNetwork GeoDataFrame ]

Compute statistical aggregations of numerical data over the network. Aggregates numerical attributes (e.g. property prices, floor areas) within network-distance thresholds, computing weighted statistics (mean, sum, min, max, variance, etc.) at each node.

Wraps compute_stats. All additional keyword arguments are forwarded; see that function for the full parameter list including stats_column_labels, distances, minutes, decay_fn, and angular. decay_fn accepts a single expression or a {label: expression} dict that computes several decay variants in one network traversal, each label suffixing its output columns.

Parameters

data_gdf
GeoDataFrame

A GeoDataFrame of data points with numerical columns.

Returns

self
CityNetwork

Returns self with statistical columns added to nodes_gdf.

data_gdf
GeoDataFrame

The input data GeoDataFrame with nearest network assignments.

Notes

from cityseer import decay

cn, prices_gdf = cn.compute_stats(
    data_gdf=prices_gdf,
    stats_column_labels=["price", "floor_area"],
    distances=[800, 1600],
    decay_fn=decay.exponential(),
)
# Weighted mean of "price" at 800m
print(cn.nodes_gdf["cc_price_mean_800"])
# Weighted sum of "floor_area" at 1600m
print(cn.nodes_gdf["cc_floor_area_sum_1600"])

add_gtfs

add_gtfs
(
self
gtfs_path : str
*
crs
max_netw_assign_dist : int = 400
)->[ CityNetwork ]

Add GTFS (General Transit Feed Specification) public transport data to the network. Integrates transit stops and routes so that centrality and accessibility analyses account for public transport connections.

Wraps io.add_transport_gtfs.

Parameters

gtfs_path
str

Path to a GTFS zip file or directory.

crs
Any

Optional CRS override for the GTFS data.

max_netw_assign_dist
int

Maximum distance (metres) for snapping transit stops to the nearest network node. Stops beyond this distance are excluded. Defaults to 400.

Returns

self
CityNetwork

Returns self for method chaining.

to_nx

to_nx
(
self
)->[ MultiDiGraph ]

Convert the network to a NetworkX MultiGraph (or MultiDiGraph if directed). If the network was built with from_nx, returns a copy of the original graph with computed centrality and layer columns added to each edge’s data dictionary. Otherwise builds a new cityseer-compatible undirected graph from the internal GeoDataFrame.

Returns

graph
nx.MultiGraph | nx.MultiDiGraph

A primal edge graph with computed metrics added to edge data.

Raises

NotImplementedError

If the network is directed but was not built via from_nx (no source graph to export).

Notes

cn = CityNetwork.from_nx(G)
cn.centrality_shortest(distances=[800])

# Round-trip: get back a NetworkX graph with metrics on edges
G_with_metrics = cn.to_nx()
u, v, k, data = list(G_with_metrics.edges(keys=True, data=True))[0]
print(data["cc_harmonic_800"])