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%20From%20Results%20to%20Maps%0A%0A%20%20%20%20This%20notebook%20bridges%20the%20gap%20between%20computing%20metrics%20and%20delivering%20them%3A%20it%20runs%20a%20quick%20centrality%20analysis%20on%20the%20bundled%20street%20network%2C%20exports%20the%20results%20to%20a%20GeoPackage%20ready%20for%20QGIS%2C%20and%20builds%20a%20publication-quality%20map%20figure%20directly%20in%20matplotlib.%0A%0A%20%20%20%20Analysis%20rarely%20ends%20at%20a%20%60GeoDataFrame%60.%20The%20two%20deliverables%20that%20most%20projects%20need%20are%20a%20GIS%20layer%20that%20collaborators%20can%20style%20and%20overlay%2C%20and%20a%20finished%20figure%20for%20a%20report%20or%20paper.%20Both%20start%20from%20the%20same%20exported%20results.%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%20import%20numpy%20as%20np%0A%0A%20%20%20%20return%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%20%23%23%20Compute%20something%20to%20map%0A%0A%20%20%20%20We%20load%20the%20bundled%20street%20network%20and%20clip%20it%20to%20a%202km%20study%20area%20around%20the%20city%20centre.%20The%20clip%20extends%20a%20further%20800m%20beyond%20the%20study%20area%2C%20matching%20the%20maximum%20analysis%20distance%2C%20so%20that%20metrics%20near%20the%20study%20boundary%20are%20not%20distorted%20by%20missing%20network%3B%20the%20%60boundary%60%20argument%20marks%20the%20nodes%20inside%20the%20study%20area%20as%20%60live%60.%20The%20high-level%20%5B%60CityNetwork%60%5D(https%3A%2F%2Fcityseer.benchmarkurbanism.com%2Fapi%2Fnetwork)%20class%20handles%20the%20dual-graph%20preparation%2C%20and%20its%20default%20%60centrality_shortest%60%20computes%20a%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_(gpd%2C%20mo)%3A%0A%20%20%20%20from%20cityseer.network%20import%20CityNetwork%0A%0A%20%20%20%20data_dir%20%3D%20(mo.notebook_dir()%20%2F%20%22..%22%20%2F%20%22..%22%20%2F%20%22data%22).resolve()%0A%20%20%20%20streets_gpd%20%3D%20gpd.read_file(data_dir%20%2F%20%22madrid_streets%22%20%2F%20%22street_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%202km%20study%20area%20around%20the%20city%20centre%2C%20with%20the%20network%20buffered%20a%20further%0A%20%20%20%20%23%20800m%20(the%20maximum%20analysis%20distance)%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(2000).iloc%5B0%5D%0A%20%20%20%20buffered_poly%20%3D%20centre.buffer(2800).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%20cn.centrality_shortest(distances%3D%5B800%5D)%0A%20%20%20%20results_gdf%20%3D%20cn.to_geopandas()%0A%20%20%20%20print(f%22%7Bcn.node_count%7D%20street%20segments%2C%20%7Bint(results_gdf.live.sum())%7D%20live%22)%0A%20%20%20%20return%20(results_gdf%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%20Export%20to%20a%20GeoPackage%0A%0A%20%20%20%20%60to_geopandas%60%20returns%20the%20results%20joined%20back%20onto%20the%20original%20LineString%20geometries%2C%20so%20the%20export%20is%20a%20standard%20%60geopandas%60%20save.%20The%20file%20contains%20one%20row%20per%20street%20segment%20with%20the%20computed%20metric%20columns%20(%60cc_harmonic_800%60%20and%20%60cc_betweenness_800%60)%2C%20the%20%60live%60%20flag%20marking%20segments%20inside%20the%20study%20area%2C%20any%20columns%20carried%20through%20from%20the%20input%20data%2C%20and%20the%20geometry%20in%20the%20projected%20CRS%20of%20the%20source%20network%20(EPSG%3A25830%2C%20UTM%20zone%2030N).%20Because%20the%20CRS%20is%20embedded%20in%20the%20GeoPackage%2C%20GIS%20applications%20will%20position%20and%20measure%20the%20layer%20correctly%20without%20further%20configuration.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(results_gdf)%3A%0A%20%20%20%20from%20pathlib%20import%20Path%0A%0A%20%20%20%20out_dir%20%3D%20Path(%22temp%22)%0A%20%20%20%20out_dir.mkdir(exist_ok%3DTrue)%0A%20%20%20%20results_gdf.to_file(out_dir%20%2F%20%22results.gpkg%22%2C%20driver%3D%22GPKG%22)%0A%20%20%20%20print(f%22CRS%3A%20%7Bresults_gdf.crs%7D%22)%0A%20%20%20%20print(sorted(c%20for%20c%20in%20results_gdf.columns%20if%20c.startswith(%22cc_%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%20%23%23%20Styling%20in%20QGIS%0A%0A%20%20%20%20A%20typical%20QGIS%20workflow%20from%20here%3A%0A%0A%20%20%20%201.%20Drag%20%60results.gpkg%60%20into%20QGIS%2C%20or%20use%20Layer%2C%20then%20Add%20Layer%2C%20then%20Add%20Vector%20Layer.%20The%20layer%20arrives%20in%20EPSG%3A25830%20and%20aligns%20with%20any%20other%20projected%20data%20without%20reprojection.%0A%20%20%20%202.%20Open%20Layer%20Properties%2C%20then%20Symbology%2C%20and%20switch%20from%20Single%20Symbol%20to%20Graduated.%20Set%20Value%20to%20%60cc_betweenness_800%60%2C%20Mode%20to%20Equal%20Count%20(Quantile)%2C%20and%20around%207%20classes.%20Quantile%20classes%20suit%20centrality%20metrics%20because%20their%20distributions%20are%20heavily%20right-skewed%3B%20equal-interval%20classes%20would%20place%20almost%20every%20street%20in%20the%20lowest%20class.%20See%20the%20%5Binterpretation%20guide%5D(https%3A%2F%2Fcityseer.benchmarkurbanism.com%2Fguide%2Finterpretation)%20for%20why.%0A%20%20%20%203.%20Vary%20line%20width%20with%20the%20value%3A%20in%20the%20symbol%20settings%2C%20use%20a%20data-defined%20override%20on%20stroke%20width%20(or%20the%20Size%20assistant)%20scaling%20from%20roughly%200.2mm%20for%20the%20lowest%20class%20to%201mm%20for%20the%20highest%2C%20so%20high-flow%20streets%20read%20at%20a%20glance%20even%20in%20greyscale%20print.%0A%20%20%20%204.%20Use%20a%20dark%2C%20low-saturation%20basemap%20or%20a%20plain%20dark%20canvas%20so%20that%20a%20bright%20sequential%20colour%20ramp%20(such%20as%20magma%20or%20viridis)%20carries%20the%20signal.%0A%20%20%20%205.%20Add%20attribution%3A%20as%20a%20minimum%20of%20good%20conscience%2C%20cite%20the%20tools%20used%2C%20for%20%60cityseer%60%20the%20%5Bassociated%20paper%5D(https%3A%2F%2Fjournals.sagepub.com%2Fdoi%2Ffull%2F10.1177%2F23998083221133827)%2C%20and%20openly%20link%20the%20%5Bdocumentation%20website%5D(https%3A%2F%2Fcityseer.benchmarkurbanism.com)%3B%20credit%20the%20underlying%20street%20data%20per%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(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20A%20publication-quality%20figure%20in%20matplotlib%0A%0A%20%20%20%20The%20same%20principles%20apply%20when%20producing%20the%20figure%20in%20code%3A%20quantile%20class%20breaks%2C%20a%20single%20sequential%20colormap%2C%20line%20width%20following%20the%20value%2C%20and%20a%20colorbar%20labelled%20with%20the%20metric%20and%20its%20distance%20threshold.%20We%20filter%20to%20%60live%60%20segments%20so%20the%20buffer%20zone%20does%20not%20appear%20in%20the%20figure%2C%20and%20compute%20the%20breaks%20with%20%60numpy%60%20(%60np.unique%60%20guards%20against%20repeated%20break%20values%20in%20the%20zero-heavy%20tail%20of%20the%20distribution).%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(plt%2C%20results_gdf)%3A%0A%20%20%20%20%23%20betweenness%20intensity%3A%20OrRd%20rank%20style%2C%20width%20and%20colour%20by%20percentile%2C%20no%20colour%20bar%0A%20%20%20%20g%20%3D%20results_gdf%5Bresults_gdf.live%5D.copy()%0A%20%20%20%20g%5B%22_r%22%5D%20%3D%20g%5B%22cc_betweenness_800%22%5D.rank(pct%3DTrue)%0A%20%20%20%20g%20%3D%20g.sort_values(%22_r%22)%20%20%23%20strongest%20drawn%20last%2C%20on%20top%0A%20%20%20%20fig%2C%20ax%20%3D%20plt.subplots(figsize%3D(7%2C%207)%2C%20dpi%3D150)%0A%20%20%20%20g.plot(ax%3Dax%2C%20color%3Dplt.get_cmap(%22OrRd%22)(g%5B%22_r%22%5D)%2C%20linewidth%3D0.15%20%2B%202.25%20*%20g%5B%22_r%22%5D)%0A%20%20%20%20ax.set_title(%22Streets%20carrying%20pedestrian%20through-movement%20in%20the%20study%20area%22%2C%20loc%3D%22left%22)%0A%20%20%20%20ax.set_axis_off()%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%20Next%20steps%0A%0A%20%20%20%20-%20The%20%5Binterpretation%20guide%5D(https%3A%2F%2Fcityseer.benchmarkurbanism.com%2Fguide%2Finterpretation)%20covers%20how%20to%20read%20centrality%20distributions%20and%20choose%20classification%20schemes.%0A%20%20%20%20-%20The%20%5Bdatasets%20page%5D(https%3A%2F%2Fcityseer.benchmarkurbanism.com%2Fexamples%2Fdatasets)%20documents%20the%20bundled%20data%2C%20its%20sources%2C%20and%20licensing.%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
6808b28143cd7d89766ec7393fb12ae5