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%20Angular%20Distance%20Network%20Centrality%0A%0A%20%20%20%20Calculate%20angular%20(geometric%20or%20%22simplest%22)%20distance%20centralities%20from%20a%20%60geopandas%60%20%60GeoDataFrame%60.%0A%0A%20%20%20%20This%20notebook%20uses%20the%20high-level%20%5B%60CityNetwork%60%5D(https%3A%2F%2Fcityseer.benchmarkurbanism.com%2Fapi%2Fnetwork)%20class%2C%20which%20builds%20the%20dual%20graph%20required%20for%20angular%20analysis%20automatically.%0A%0A%20%20%20%20The%20bundled%20datasets%20and%20their%20source%20attributions%20are%20documented%20on%20the%20%5Bdatasets%20page%5D(https%3A%2F%2Fcityseer.benchmarkurbanism.com%2Fexamples%2Fdatasets).%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%20geopandas%20as%20gpd%0A%20%20%20%20import%20matplotlib.pyplot%20as%20plt%0A%20%20%20%20from%20cityseer.network%20import%20CityNetwork%0A%0A%20%20%20%20return%20CityNetwork%2C%20gpd%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%20Load%20the%20streets%20and%20clip%20them%20to%20a%20buffered%20study%20area.%20The%20%60boundary%60%20argument%20to%20%5B%60from_geopandas%60%5D(https%3A%2F%2Fcityseer.benchmarkurbanism.com%2Fapi%2Fnetwork%23from_geopandas)%20marks%20the%20nodes%20inside%20the%20study%20area%20as%20%60live%60%3B%20the%20surrounding%20non-live%20nodes%20prevent%20edge%20rolloff%2C%20since%20they%20are%20used%20for%20routing%20but%20metrics%20are%20not%20computed%20for%20them.%20Because%20each%20node's%20centrality%20depends%20only%20on%20its%20catchment%20within%20the%20largest%20analysis%20distance%2C%20a%20district%20plus%20a%20sufficiently%20large%20buffer%20reproduces%20the%20city-wide%20values%20for%20the%20live%20nodes%20at%20far%20lower%20cost.%20See%20the%20%5Blive%20nodes%20example%5D(https%3A%2F%2Fcityseer.benchmarkurbanism.com%2Fexamples%2Frecipes%2Flive-nodes)%20for%20more%20details.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(CityNetwork%2C%20gpd)%3A%0A%20%20%20%20streets_gpd%20%3D%20gpd.read_file(%22..%2F..%2Fdata%2Fmadrid_streets%2Fstreet_network.gpkg%22)%0A%20%20%20%20streets_gpd%20%3D%20streets_gpd.explode(ignore_index%3DTrue)%0A%20%20%20%20%23%20the%20source%20data%20contains%20a%20small%20number%20of%20exact%20duplicate%20geometries%0A%20%20%20%20streets_gpd%20%3D%20streets_gpd.drop_duplicates(subset%3D%22geometry%22)%0A%20%20%20%20%23%203km%20study%20area%20around%20the%20city%20centre%2C%20with%20the%20network%20buffered%20a%20further%2010km%0A%20%20%20%20%23%20(the%20largest%20analysis%20distance%20used%20below)%20to%20prevent%20edge%20rolloff%0A%20%20%20%20centre%20%3D%20gpd.GeoSeries.from_xy(%5B440300%5D%2C%20%5B4474300%5D%2C%20crs%3Dstreets_gpd.crs)%0A%20%20%20%20study_poly%20%3D%20centre.buffer(3000).iloc%5B0%5D%0A%20%20%20%20buffered_poly%20%3D%20centre.buffer(13000).iloc%5B0%5D%0A%20%20%20%20streets_clip%20%3D%20streets_gpd%5Bstreets_gpd.intersects(buffered_poly)%5D%0A%20%20%20%20cn%20%3D%20CityNetwork.from_geopandas(streets_clip%2C%20boundary%3Dstudy_poly)%0A%20%20%20%20return%20(cn%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%20Use%20the%20%5B%60centrality_simplest%60%5D(https%3A%2F%2Fcityseer.benchmarkurbanism.com%2Fapi%2Fnetwork%23centrality_simplest)%20method%20to%20calculate%20angular%20(geometric%20or%20%22simplest%22)%20distance%20centralities.%20Angular%20analysis%20requires%20the%20dual%20graph%2C%20which%20%60CityNetwork%60%20builds%20automatically%20during%20construction.%0A%0A%20%20%20%20The%20method%20can%20calculate%20centralities%20for%20numerous%20distances%20at%20once%20via%20the%20%60distances%60%20parameter%2C%20which%20accepts%20a%20list%20of%20distances.%20By%20default%20it%20computes%20a%20single%20angular%20harmonic%20closeness%20and%20a%20single%20angular%20betweenness.%0A%0A%20%20%20%20Results%20are%20written%20as%20columns%20named%20%60cc_%7Bcentrality%7D_%7Bdistance%7D_ang%60%2C%20and%20%60to_geopandas%60%20returns%20them%20joined%20to%20the%20original%20street%20LineString%20geometries.%20Standard%20%60geopandas%60%20functionality%20can%20be%20used%20to%20explore%2C%20visualise%2C%20or%20save%20the%20results.%20See%20the%20documentation%20for%20more%20information%20on%20the%20available%20centrality%20formulations.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(cn)%3A%0A%20%20%20%20_distances%20%3D%20%5B500%2C%202000%5D%0A%20%20%20%20cn.centrality_simplest(distances%3D_distances)%0A%20%20%20%20nodes_gdf_1%20%3D%20cn.to_geopandas()%0A%20%20%20%20nodes_gdf_1.head()%0A%20%20%20%20return%20(nodes_gdf_1%2C)%0A%0A%0A%40app.cell%0Adef%20_(nodes_gdf_1)%3A%0A%20%20%20%20nodes_gdf_1.columns%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(nodes_gdf_1)%3A%0A%20%20%20%20nodes_gdf_1%5B%22cc_betweenness_2000_ang%22%5D.describe()%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(nodes_gdf_1%2C%20plt)%3A%0A%20%20%20%20_fig%2C%20_ax%20%3D%20plt.subplots(1%2C%201%2C%20figsize%3D(7%2C%207)%2C%20dpi%3D150)%0A%20%20%20%20_g%20%3D%20nodes_gdf_1%5Bnodes_gdf_1.live%5D.copy()%0A%20%20%20%20_g%5B%22_r%22%5D%20%3D%20_g%5B%22cc_harmonic_500_ang%22%5D.rank(pct%3DTrue)%0A%20%20%20%20_g%20%3D%20_g.sort_values(%22_r%22)%20%20%23%20strongest%20drawn%20last%0A%20%20%20%20_g.plot(ax%3D_ax%2C%20color%3Dplt.get_cmap(%22OrRd%22)(_g%5B%22_r%22%5D)%2C%20linewidth%3D0.15%20%2B%202.25%20*%20_g%5B%22_r%22%5D)%0A%20%20%20%20_ax.set_title(%22Angular%20harmonic%20closeness%2C%20500%20m%22%2C%20loc%3D%22left%22)%0A%20%20%20%20_ax.set_axis_off()%0A%20%20%20%20_fig.tight_layout()%0A%20%20%20%20_fig%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(nodes_gdf_1%2C%20plt)%3A%0A%20%20%20%20_fig%2C%20_ax%20%3D%20plt.subplots(1%2C%201%2C%20figsize%3D(7%2C%207)%2C%20dpi%3D150)%0A%20%20%20%20_g%20%3D%20nodes_gdf_1%5Bnodes_gdf_1.live%5D.copy()%0A%20%20%20%20_g%5B%22_r%22%5D%20%3D%20_g%5B%22cc_betweenness_2000_ang%22%5D.rank(pct%3DTrue)%0A%20%20%20%20_g%20%3D%20_g.sort_values(%22_r%22)%20%20%23%20strongest%20drawn%20last%0A%20%20%20%20_g.plot(ax%3D_ax%2C%20color%3Dplt.get_cmap(%22OrRd%22)(_g%5B%22_r%22%5D)%2C%20linewidth%3D0.15%20%2B%202.25%20*%20_g%5B%22_r%22%5D)%0A%20%20%20%20_ax.set_title(%22Angular%20betweenness%2C%202000%20m%22%2C%20loc%3D%22left%22)%0A%20%20%20%20_ax.set_axis_off()%0A%20%20%20%20_fig.tight_layout()%0A%20%20%20%20_fig%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%20Alternatively%2C%20you%20can%20define%20the%20distance%20thresholds%20using%20a%20list%20of%20%60minutes%60%20instead.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(cn%2C%20nodes_gdf_1)%3A%0A%20%20%20%20cn.centrality_simplest(minutes%3D%5B15%5D)%0A%20%20%20%20nodes_gdf_2%20%3D%20cn.to_geopandas()%0A%20%20%20%20nodes_gdf_2%5B%5Bc%20for%20c%20in%20nodes_gdf_2.columns%20if%20c%20not%20in%20nodes_gdf_1.columns%5D%5D.head()%0A%20%20%20%20return%20(nodes_gdf_2%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%20The%20method%20will%20map%20the%20minutes%20values%20into%20the%20equivalent%20distances%2C%20which%20are%20reported%20in%20the%20logged%20output.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(nodes_gdf_2)%3A%0A%20%20%20%20nodes_gdf_2.columns%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%20As%20per%20the%20function%20logging%20outputs%2C%2015%20minutes%20has%20been%20mapped%20to%201200m%20at%20default%20%60speed_m_s%60%2C%20so%20the%20corresponding%20outputs%20can%20be%20visualised%20using%20the%201200m%20columns.%20Use%20the%20configurable%20%60speed_m_s%60%20parameter%20to%20set%20a%20custom%20metres%20per%20second%20walking%20speed.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(nodes_gdf_2%2C%20plt)%3A%0A%20%20%20%20_fig%2C%20_ax%20%3D%20plt.subplots(1%2C%201%2C%20figsize%3D(7%2C%207)%2C%20dpi%3D150)%0A%20%20%20%20_g%20%3D%20nodes_gdf_2%5Bnodes_gdf_2.live%5D.copy()%0A%20%20%20%20_g%5B%22_r%22%5D%20%3D%20_g%5B%22cc_harmonic_1200_ang%22%5D.rank(pct%3DTrue)%0A%20%20%20%20_g%20%3D%20_g.sort_values(%22_r%22)%20%20%23%20strongest%20drawn%20last%0A%20%20%20%20_g.plot(ax%3D_ax%2C%20color%3Dplt.get_cmap(%22OrRd%22)(_g%5B%22_r%22%5D)%2C%20linewidth%3D0.15%20%2B%202.25%20*%20_g%5B%22_r%22%5D)%0A%20%20%20%20_ax.set_title(%22Angular%20harmonic%20closeness%2C%201200%20m%20(15%20minutes)%22%2C%20loc%3D%22left%22)%0A%20%20%20%20_ax.set_axis_off()%0A%20%20%20%20_fig.tight_layout()%0A%20%20%20%20_fig%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%20Sampled%20centrality%20for%20larger%20distances%0A%20%20%20%20%22%22%22)%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%20For%20larger%20distance%20thresholds%2C%20the%20computational%20cost%20increases%20substantially.%20The%20%60centrality_simplest%60%20method%20supports%20a%20%60sample%3DTrue%60%20parameter%20that%20uses%20a%20sampling%20strategy%20to%20compute%20centralities%20more%20efficiently%20at%20larger%20scales%20while%20maintaining%20a%20relatively%20high%20degree%20of%20rank%20performance.%20Use%20non-sampled%20computation%20for%20publication-quality%20results.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(cn%2C%20nodes_gdf_2)%3A%0A%20%20%20%20_distances%20%3D%20%5B10000%5D%0A%20%20%20%20cn.centrality_simplest(distances%3D_distances%2C%20sample%3DTrue)%0A%20%20%20%20nodes_gdf_3%20%3D%20cn.to_geopandas()%0A%20%20%20%20sorted(c%20for%20c%20in%20nodes_gdf_3.columns%20if%20c%20not%20in%20nodes_gdf_2.columns)%0A%20%20%20%20return%20(nodes_gdf_3%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%20For%20shorter%20distances%20where%20the%20number%20of%20reachable%20nodes%20is%20small%2C%20full%20computation%20is%20used.%20For%20larger%20distances%2C%20the%20function%20applies%20sampling%20to%20achieve%20the%20target%20accuracy%20efficiently.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(nodes_gdf_3%2C%20plt)%3A%0A%20%20%20%20_fig%2C%20_ax%20%3D%20plt.subplots(1%2C%201%2C%20figsize%3D(7%2C%207)%2C%20dpi%3D150)%0A%20%20%20%20_g%20%3D%20nodes_gdf_3%5Bnodes_gdf_3.live%5D.copy()%0A%20%20%20%20_g%5B%22_r%22%5D%20%3D%20_g%5B%22cc_betweenness_10000_ang%22%5D.rank(pct%3DTrue)%0A%20%20%20%20_g%20%3D%20_g.sort_values(%22_r%22)%20%20%23%20strongest%20drawn%20last%0A%20%20%20%20_g.plot(ax%3D_ax%2C%20color%3Dplt.get_cmap(%22OrRd%22)(_g%5B%22_r%22%5D)%2C%20linewidth%3D0.15%20%2B%202.25%20*%20_g%5B%22_r%22%5D)%0A%20%20%20%20_ax.set_title(%22Angular%20betweenness%2C%2010000%20m%20(sampled)%22%2C%20loc%3D%22left%22)%0A%20%20%20%20_ax.set_axis_off()%0A%20%20%20%20_fig.tight_layout()%0A%20%20%20%20_fig%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%20Tolerance%20for%20near-simplest%20paths%0A%20%20%20%20%22%22%22)%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%20The%20%60tolerance%60%20parameter%20allows%20betweenness%20to%20count%20paths%20that%20are%20within%20a%20given%20percentage%20of%20the%20simplest%20(lowest%20angular%20cost)%20path.%20For%20angular%20centrality%2C%20a%20larger%20tolerance%20(e.g.%20%60tolerance%3D20.0%60)%20can%20be%20appropriate%20since%20angular%20costs%20are%20more%20ambiguous%20than%20metric%20distances.%20This%20produces%20more%20distributed%20betweenness%20distributions%20by%20accounting%20for%20near-optimal%20route%20choices.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(cn%2C%20nodes_gdf_3)%3A%0A%20%20%20%20_distances%20%3D%20%5B5000%5D%0A%20%20%20%20cn.centrality_simplest(distances%3D_distances%2C%20sample%3DTrue%2C%20tolerance%3D20.0)%0A%20%20%20%20nodes_gdf_4%20%3D%20cn.to_geopandas()%0A%20%20%20%20sorted(c%20for%20c%20in%20nodes_gdf_4.columns%20if%20c%20not%20in%20nodes_gdf_3.columns)%0A%20%20%20%20return%20(nodes_gdf_4%2C)%0A%0A%0A%40app.cell%0Adef%20_(nodes_gdf_4%2C%20plt)%3A%0A%20%20%20%20_fig%2C%20_ax%20%3D%20plt.subplots(1%2C%201%2C%20figsize%3D(7%2C%207)%2C%20dpi%3D150)%0A%20%20%20%20_g%20%3D%20nodes_gdf_4%5Bnodes_gdf_4.live%5D.copy()%0A%20%20%20%20_g%5B%22_r%22%5D%20%3D%20_g%5B%22cc_betweenness_5000_ang%22%5D.rank(pct%3DTrue)%0A%20%20%20%20_g%20%3D%20_g.sort_values(%22_r%22)%20%20%23%20strongest%20drawn%20last%0A%20%20%20%20_g.plot(ax%3D_ax%2C%20color%3Dplt.get_cmap(%22OrRd%22)(_g%5B%22_r%22%5D)%2C%20linewidth%3D0.15%20%2B%202.25%20*%20_g%5B%22_r%22%5D)%0A%20%20%20%20_ax.set_title(%22Angular%20betweenness%2C%205000%20m%20(tolerance%2020%25)%22%2C%20loc%3D%22left%22)%0A%20%20%20%20_ax.set_axis_off()%0A%20%20%20%20_fig.tight_layout()%0A%20%20%20%20_fig%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%20Summary%0A%0A%20%20%20%20This%20notebook%20demonstrated%20how%20to%20calculate%20angular%20(simplest-path)%20centralities%20from%20a%20%60geopandas%60%20%60GeoDataFrame%60%20with%20the%20%60CityNetwork%60%20class.%20Angular%20analysis%20weights%20paths%20by%20cumulative%20turning%20angle%20rather%20than%20distance%2C%20capturing%20route%20directness%20and%20identifying%20streets%20that%20form%20part%20of%20straighter%2C%20more%20navigable%20corridors.%20It%20also%20showed%20how%20to%20use%20the%20%60sample%60%20parameter%20for%20efficient%20computation%20at%20larger%20distance%20thresholds%2C%20and%20the%20%60tolerance%60%20parameter%20for%20near-simplest%20path%20betweenness.%0A%0A%20%20%20%20**Next%20steps%3A**%0A%0A%20%20%20%20-%20To%20calculate%20metric%20(shortest-path)%20centralities%20instead%2C%20see%20%5BMetric%20Centrality%5D(https%3A%2F%2Fcityseer.benchmarkurbanism.com%2Fexamples%2Fcentrality%2Fgpd-metric-centrality).%0A%20%20%20%20-%20To%20calculate%20centralities%20directly%20from%20OpenStreetMap%20data%2C%20see%20%5BOSM%20Centrality%5D(https%3A%2F%2Fcityseer.benchmarkurbanism.com%2Fexamples%2Fcentrality%2Fosm-centrality).%0A%20%20%20%20-%20To%20compute%20accessibility%20or%20mixed-use%20metrics%20over%20the%20same%20network%2C%20see%20the%20%5BAccessibility%5D(https%3A%2F%2Fcityseer.benchmarkurbanism.com%2Fexamples%2Faccessibility)%20recipes.%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
67316315144215ac1ea538c1478d7f4c