import%20marimo%0A%0A__generated_with%20%3D%20%220.23.13%22%0Aapp%20%3D%20marimo.App()%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20import%20marimo%20as%20mo%0A%0A%20%20%20%20return%20(mo%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%20Directed%20Networks%20and%20One-Way%20Streets%0A%0A%20%20%20%20By%20default%2C%20%60cityseer%60%20builds%20undirected%20networks%20in%20which%20every%20street%20can%20be%20traversed%20in%20both%20directions.%20That%20is%20the%20correct%20model%20for%20pedestrian%20analysis.%20For%20cycling%20or%20vehicular%20questions%2C%20one-way%20restrictions%20change%20the%20available%20routes%2C%20and%20with%20them%20betweenness%20and%20closeness%20patterns.%20From%20v5%2C%20%60cityseer%60%20supports%20directed%20networks%3A%20one-way%20streets%20are%20restricted%20to%20their%20designated%20direction%20while%20two-way%20streets%20remain%20bidirectional.%0A%0A%20%20%20%20There%20are%20three%20ways%20to%20obtain%20a%20directed%20network%3A%0A%0A%20%20%20%20-%20%60CityNetwork.from_geopandas(gdf%2C%20directed%3DTrue)%60%20with%20a%20boolean%20%60oneway%60%20column%2C%20where%20one-way%20features%20run%20in%20their%20LineString%20coordinate%20order%3B%0A%20%20%20%20-%20%60CityNetwork.from_nx(G)%60%20with%20a%20NetworkX%20%60MultiDiGraph%60%2C%20which%20enables%20directed%20mode%20automatically%3B%0A%20%20%20%20-%20OSM%20data%20via%20%5BOSMnx%5D(https%3A%2F%2Fosmnx.readthedocs.io%2F)%2C%20converted%20with%20%60io.nx_from_osm_nx(G_osmnx%2C%20directed%3DTrue)%60%2C%20shown%20below.%0A%0A%20%20%20%20Note%20that%20the%20graph%20cleaning%20functions%20in%20%60tools.graphs%60%20do%20not%20preserve%20edge%20directionality%3A%20pass%20directed%20graphs%20directly%20to%20the%20constructors%20rather%20than%20through%20manual%20simplification.%20See%20the%20%5Bguide%5D(https%3A%2F%2Fcityseer.benchmarkurbanism.com%2Fguide%2Fnetworks%23directed-graphs-and-one-way-streets).%0A%0A%20%20%20%20Network%20data%20%C2%A9%20%5BOpenStreetMap%5D(https%3A%2F%2Fwww.openstreetmap.org%2Fcopyright)%20contributors%2C%20available%20under%20the%20Open%20Database%20Licence.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20import%20matplotlib.pyplot%20as%20plt%0A%20%20%20%20import%20osmnx%20as%20ox%0A%20%20%20%20from%20cityseer.network%20import%20CityNetwork%0A%20%20%20%20from%20cityseer.tools%20import%20io%0A%0A%20%20%20%20return%20CityNetwork%2C%20io%2C%20ox%2C%20plt%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Fetching%20a%20drive%20network%0A%0A%20%20%20%20We%20fetch%20the%20drivable%20network%20for%20the%20city%20centre%2C%20where%20one-way%20circulation%20is%20common%2C%20and%20project%20it%20to%20a%20metric%20CRS.%20OSMnx%20returns%20a%20%60MultiDiGraph%60%20whose%20edges%20carry%20%60oneway%60%20attributes.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(ox)%3A%0A%20%20%20%20G_osmnx%20%3D%20ox.graph.graph_from_point(%0A%20%20%20%20%20%20%20%20(40.4169%2C%20-3.7035)%2C%20%20%23%20city%20centre%20(lat%2C%20lng)%0A%20%20%20%20%20%20%20%20dist%3D1200%2C%0A%20%20%20%20%20%20%20%20network_type%3D%22drive%22%2C%0A%20%20%20%20%20%20%20%20simplify%3DTrue%2C%0A%20%20%20%20)%0A%20%20%20%20G_osmnx_proj%20%3D%20ox.projection.project_graph(G_osmnx%2C%20to_crs%3D%22EPSG%3A25830%22)%0A%20%20%20%20print(f%22%7BG_osmnx_proj.number_of_nodes()%7D%20nodes%2C%20%7BG_osmnx_proj.number_of_edges()%7D%20directed%20edges%22)%0A%20%20%20%20return%20(G_osmnx_proj%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Undirected%20and%20directed%20variants%0A%0A%20%20%20%20We%20convert%20the%20same%20OSMnx%20graph%20twice%3A%20once%20discarding%20directionality%20(the%20default)%20and%20once%20retaining%20it.%20Both%20are%20wrapped%20in%20the%20high-level%20%60CityNetwork%60%20class%2C%20which%20builds%20the%20dual%20graph%20and%20computes%20centrality%20with%20lean%20defaults%20(a%20single%20harmonic%20closeness%20and%20a%20single%20betweenness).%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(CityNetwork%2C%20G_osmnx_proj%2C%20io)%3A%0A%20%20%20%20G_undirected%20%3D%20io.nx_from_osm_nx(G_osmnx_proj%2C%20directed%3DFalse)%0A%20%20%20%20cn_undirected%20%3D%20CityNetwork.from_nx(G_undirected)%0A%20%20%20%20print(%22undirected%3A%22%2C%20cn_undirected.is_directed)%0A%0A%20%20%20%20G_directed%20%3D%20io.nx_from_osm_nx(G_osmnx_proj%2C%20directed%3DTrue)%0A%20%20%20%20cn_directed%20%3D%20CityNetwork.from_nx(G_directed)%0A%20%20%20%20print(%22directed%3A%22%2C%20cn_directed.is_directed)%0A%20%20%20%20return%20cn_directed%2C%20cn_undirected%0A%0A%0A%40app.cell%0Adef%20_(cn_directed%2C%20cn_undirected)%3A%0A%20%20%20%20cn_undirected.centrality_shortest(distances%3D%5B800%5D)%0A%20%20%20%20cn_directed.centrality_shortest(distances%3D%5B800%5D)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Comparing%20the%20results%0A%0A%20%20%20%20One-way%20restrictions%20lengthen%20many%20round-trip%20routes%2C%20which%20redistributes%20through-movement%3A%20betweenness%20concentrates%20on%20the%20streets%20that%20carry%20the%20permitted%20directions%20of%20flow.%0A%0A%20%20%20%20Two%20definitional%20points%20for%20directed%20betweenness%3A%0A%0A%20%20%20%20-%20each%20**ordered**%20origin-destination%20pair%20contributes%20with%20weight%201%2C%20so%20on%20a%20fully%20two-way%20network%20the%20directed%20values%20are%20twice%20the%20undirected%20magnitudes%20(A%20to%20B%20and%20B%20to%20A%20are%20counted%20separately)%3B%0A%20%20%20%20-%20routes%20are%20only%20counted%20where%20a%20permitted%20directed%20path%20exists%20within%20the%20distance%20threshold.%0A%0A%20%20%20%20For%20like-for-like%20visual%20comparison%20we%20therefore%20plot%20each%20variant%20on%20its%20own%20scale.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(cn_directed%2C%20cn_undirected%2C%20plt)%3A%0A%20%20%20%20fig%2C%20axes%20%3D%20plt.subplots(1%2C%202%2C%20figsize%3D(12%2C%206)%2C%20dpi%3D150)%0A%20%20%20%20for%20ax%2C%20cn%2C%20title%20in%20(%0A%20%20%20%20%20%20%20%20(axes%5B0%5D%2C%20cn_undirected%2C%20%22undirected%22)%2C%0A%20%20%20%20%20%20%20%20(axes%5B1%5D%2C%20cn_directed%2C%20%22directed%20(one-way%20aware)%22)%2C%0A%20%20%20%20)%3A%0A%20%20%20%20%20%20%20%20streets%20%3D%20cn.to_geopandas()%20%20%23%20values%20projected%20back%20onto%20the%20street%20segments%0A%20%20%20%20%20%20%20%20streets%20%3D%20streets%5Bstreets.live%5D.copy()%0A%20%20%20%20%20%20%20%20streets%5B%22_r%22%5D%20%3D%20streets%5B%22cc_betweenness_800%22%5D.rank(pct%3DTrue)%0A%20%20%20%20%20%20%20%20streets%20%3D%20streets.sort_values(%22_r%22)%20%20%23%20strongest%20drawn%20last%2C%20on%20top%0A%20%20%20%20%20%20%20%20streets.plot(ax%3Dax%2C%20color%3Dplt.get_cmap(%22OrRd%22)(streets%5B%22_r%22%5D)%2C%20linewidth%3D0.15%20%2B%202.25%20*%20streets%5B%22_r%22%5D)%0A%20%20%20%20%20%20%20%20ax.set_title(f%22Betweenness%2C%20800%20m%20(%7Btitle%7D)%22%2C%20loc%3D%22left%22)%0A%20%20%20%20%20%20%20%20ax.set_axis_off()%0A%20%20%20%20%20%20%20%20ax.set_aspect(%22equal%22)%0A%20%20%20%20fig.tight_layout()%0A%20%20%20%20fig%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Practical%20notes%0A%0A%20%20%20%20-%20Directed%20mode%20composes%20with%20the%20other%20v5%20features%3A%20expression%20dictionaries%2C%20%5Bdemand-weighted%20betweenness%5D(https%3A%2F%2Fcityseer.benchmarkurbanism.com%2Fexamples%2Fcentrality%2Fod-betweenness)%2C%20edge%20impedances%2C%20and%20elevation-aware%20costs%20all%20work%20on%20directed%20networks.%0A%20%20%20%20-%20For%20a%20GeoDataFrame%20source%2C%20set%20%60oneway%3DTrue%60%20on%20the%20one-way%20features%20and%20check%20the%20digitised%20direction%20of%20the%20geometry%3A%20one-way%20streets%20flow%20in%20LineString%20coordinate%20order.%0A%20%20%20%20-%20Keep%20pedestrian%20analyses%20undirected%3B%20footways%20are%20not%20directional%2C%20and%20one-way%20restrictions%20apply%20to%20vehicles.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20app.run()%0A
987e50466f7fd67fa30819fcbe869e86