diff --git a/docs/plots/plots.py b/docs/plots/plots.py index 796d4cfe..18010a86 100644 --- a/docs/plots/plots.py +++ b/docs/plots/plots.py @@ -94,7 +94,7 @@ buffer = 1250 poly_wgs, _ = io.buffered_point_poly(lng, lat, buffer) graph_raw = io.osm_graph_from_poly(poly_wgs, simplify=False) -graph_utm = io.osm_graph_from_poly(poly_wgs, simplify=True, iron_edges=True) +graph_utm = io.osm_graph_from_poly(poly_wgs, simplify=True) # plot buffer buffered_point, _ = io.buffered_point_poly(lng, lat, 750, projected=True) min_x, min_y, max_x, max_y = buffered_point.bounds @@ -116,25 +116,8 @@ def simple_plot(_G, _path, plot_geoms=True): ) -simple_plot(graph_raw, f"{IMAGES_PATH}/graph_cleaning_1.{FORMAT}", plot_geoms=False) -simple_plot(graph_utm, f"{IMAGES_PATH}/graph_cleaning_1b.{FORMAT}") - -graph_utm = graphs.nx_simple_geoms(graph_raw) -graph_utm = graphs.nx_remove_filler_nodes(graph_utm) -graph_utm = graphs.nx_remove_dangling_nodes(graph_utm, despine=15, remove_disconnected=10) -simple_plot(graph_utm, f"{IMAGES_PATH}/graph_cleaning_2.{FORMAT}") -# first pass of consolidation -graph_utm = graphs.nx_consolidate_nodes(graph_utm, buffer_dist=15, crawl=True) -simple_plot(graph_utm, f"{IMAGES_PATH}/graph_cleaning_3.{FORMAT}") -# split opposing line geoms to facilitate parallel merging -graph_utm = graphs.nx_split_opposing_geoms(graph_utm, buffer_dist=15) -simple_plot(graph_utm, f"{IMAGES_PATH}/graph_cleaning_4.{FORMAT}") -# second pass of consolidation -graph_utm = graphs.nx_consolidate_nodes(graph_utm, buffer_dist=15, crawl=False, neighbour_policy="indirect") -simple_plot(graph_utm, f"{IMAGES_PATH}/graph_cleaning_5.{FORMAT}") -# iron edges -graph_utm = graphs.nx_iron_edges(graph_utm) -simple_plot(graph_utm, f"{IMAGES_PATH}/graph_cleaning_6.{FORMAT}") +simple_plot(graph_raw, f"{IMAGES_PATH}/graph_raw.{FORMAT}") +simple_plot(graph_utm, f"{IMAGES_PATH}/graph_clean.{FORMAT}") # LAYERS MODULE # show assignment to network # random seed 25 diff --git a/docs/public/images/assignment.png b/docs/public/images/assignment.png index 851839cf..4db50d53 100644 Binary files a/docs/public/images/assignment.png and b/docs/public/images/assignment.png differ diff --git a/docs/public/images/assignment_decomposed.png b/docs/public/images/assignment_decomposed.png index e024b9c9..f1c1246d 100644 Binary files a/docs/public/images/assignment_decomposed.png and b/docs/public/images/assignment_decomposed.png differ diff --git a/docs/public/images/assignment_plot.png b/docs/public/images/assignment_plot.png index 18289759..ce1fb86b 100644 Binary files a/docs/public/images/assignment_plot.png and b/docs/public/images/assignment_plot.png differ diff --git a/docs/public/images/betas.png b/docs/public/images/betas.png index 920100e6..c3c94215 100644 Binary files a/docs/public/images/betas.png and b/docs/public/images/betas.png differ diff --git a/docs/public/images/graph.png b/docs/public/images/graph.png index 7554c8b9..fbf299ca 100644 Binary files a/docs/public/images/graph.png and b/docs/public/images/graph.png differ diff --git a/docs/public/images/graph_clean.png b/docs/public/images/graph_clean.png new file mode 100644 index 00000000..2db97eae Binary files /dev/null and b/docs/public/images/graph_clean.png differ diff --git a/docs/public/images/graph_colour.png b/docs/public/images/graph_colour.png index 1018d337..ee632de8 100644 Binary files a/docs/public/images/graph_colour.png and b/docs/public/images/graph_colour.png differ diff --git a/docs/public/images/graph_decomposed.png b/docs/public/images/graph_decomposed.png index d24853df..1a3fbc28 100644 Binary files a/docs/public/images/graph_decomposed.png and b/docs/public/images/graph_decomposed.png differ diff --git a/docs/public/images/graph_dual.png b/docs/public/images/graph_dual.png index 01ae5a1f..67cfee01 100644 Binary files a/docs/public/images/graph_dual.png and b/docs/public/images/graph_dual.png differ diff --git a/docs/public/images/graph_example.png b/docs/public/images/graph_example.png index c8f0775e..11ac6a85 100644 Binary files a/docs/public/images/graph_example.png and b/docs/public/images/graph_example.png differ diff --git a/docs/public/images/graph_raw.png b/docs/public/images/graph_raw.png new file mode 100644 index 00000000..c6bf43a0 Binary files /dev/null and b/docs/public/images/graph_raw.png differ diff --git a/docs/public/images/graph_simple.png b/docs/public/images/graph_simple.png index 503136d8..da0ea8f9 100644 Binary files a/docs/public/images/graph_simple.png and b/docs/public/images/graph_simple.png differ diff --git a/docs/public/images/intro_mixed_uses.png b/docs/public/images/intro_mixed_uses.png index b70bd693..26fd801f 100644 Binary files a/docs/public/images/intro_mixed_uses.png and b/docs/public/images/intro_mixed_uses.png differ diff --git a/docs/public/images/intro_segment_harmonic.png b/docs/public/images/intro_segment_harmonic.png index a557c54b..6202c6df 100644 Binary files a/docs/public/images/intro_segment_harmonic.png and b/docs/public/images/intro_segment_harmonic.png differ diff --git a/docs/src/pages/rustalgos/rustalgos.md b/docs/src/pages/rustalgos/rustalgos.md index e66b1623..c50ecb73 100644 --- a/docs/src/pages/rustalgos/rustalgos.md +++ b/docs/src/pages/rustalgos/rustalgos.md @@ -535,7 +535,7 @@ Overriding the default $w_{min}$ will adjust the $d_{max}$ accordingly, for exam </div> <span class="pt">)->[</span> <span class="pr">list[int]</span> - <span class="pr">list[float</span> + <span class="pr">list[float]</span> <span class="pt">]</span> </div> </div> @@ -1903,7 +1903,7 @@ datapoints are not located with high spatial precision. </div> <span class="pt">)->[</span> <span class="pr">list[int]</span> - <span class="pr">NodeVisit</span> + <span class="pr">NodeVisit]</span> <span class="pt">]</span> </div> </div> @@ -1940,7 +1940,7 @@ datapoints are not located with high spatial precision. </div> <span class="pt">)->[</span> <span class="pr">list[int]</span> - <span class="pr">NodeVisit</span> + <span class="pr">NodeVisit]</span> <span class="pt">]</span> </div> </div> @@ -1979,7 +1979,7 @@ datapoints are not located with high spatial precision. <span class="pr">list[int]</span> <span class="pr">list[int]</span> <span class="pr">NodeVisit]</span> - <span class="pr">EdgeVisit</span> + <span class="pr">EdgeVisit]</span> <span class="pt">]</span> </div> </div> @@ -2807,22 +2807,22 @@ datapoints are not located with high spatial precision. -<span class="name">node_xys</span><span class="annotation">: list[tuple[float, float]]</span> +<span class="name">node_ys</span><span class="annotation">: list[float]</span> -<span class="name">node_xs</span><span class="annotation">: list[float]</span> +<span class="name">node_lives</span><span class="annotation">: list[bool]</span> -<span class="name">node_ys</span><span class="annotation">: list[float]</span> +<span class="name">node_xs</span><span class="annotation">: list[float]</span> -<span class="name">node_lives</span><span class="annotation">: list[bool]</span> +<span class="name">node_xys</span><span class="annotation">: list[tuple[float, float]]</span> @@ -2999,7 +2999,6 @@ datapoints are not located with high spatial precision. <span class="pa"> bool = False</span> </div> <span class="pt">)->[</span> - <span class="pr">Union[Buffer</span> <span class="pr">Any]]</span> <span class="pr">Any]]]</span> <span class="pr">bool</span> @@ -3009,7 +3008,6 @@ datapoints are not located with high spatial precision. <span class="pr">str</span> <span class="pr">bytes</span> <span class="pr">_NestedSequence[bool | int | float | complex | str | bytes]]</span> - <span class="pr">Union[Buffer</span> <span class="pr">Any]]</span> <span class="pr">Any]]]</span> <span class="pr">bool</span> @@ -3019,7 +3017,6 @@ datapoints are not located with high spatial precision. <span class="pr">str</span> <span class="pr">bytes</span> <span class="pr">_NestedSequence[bool | int | float | complex | str | bytes]]</span> - <span class="pr">Union[Buffer</span> <span class="pr">Any]]</span> <span class="pr">Any]]]</span> <span class="pr">bool</span> @@ -3028,7 +3025,7 @@ datapoints are not located with high spatial precision. <span class="pr">complex</span> <span class="pr">str</span> <span class="pr">bytes</span> - <span class="pr">_NestedSequence[bool | int | float | complex | str | bytes</span> + <span class="pr">_NestedSequence[bool | int | float | complex | str | bytes]]</span> <span class="pt">]</span> </div> </div> @@ -3072,7 +3069,6 @@ datapoints are not located with high spatial precision. <span class="pa"> bool = False</span> </div> <span class="pt">)->[</span> - <span class="pr">Union[Buffer</span> <span class="pr">dtype[Any]]</span> <span class="pr">dtype[Any]]]</span> <span class="pr">bool</span> diff --git a/docs/src/pages/tools/graphs.md b/docs/src/pages/tools/graphs.md index 37c5ea66..4b5eaabc 100644 --- a/docs/src/pages/tools/graphs.md +++ b/docs/src/pages/tools/graphs.md @@ -331,11 +331,21 @@ side-effects as a function of varied node intensities when computing network cen <div class="content"> -<span class="name">nx_iron_edges</span><div class="signature"> +<span class="name">nx_iron_edges</span><div class="signature multiline"> <span class="pt">(</span> <div class="param"> <span class="pn">nx_multigraph</span> </div> + <div class="param"> + <span class="pn">simplify_by_angle</span> + <span class="pc">:</span> + <span class="pa"> int = 100</span> + </div> + <div class="param"> + <span class="pn">min_self_loop_length</span> + <span class="pc">:</span> + <span class="pa"> int = 100</span> + </div> <span class="pt">)</span> </div> </div> @@ -353,6 +363,26 @@ side-effects as a function of varied node intensities when computing network cen A `networkX` `MultiGraph` in a projected coordinate system, containing `x` and `y` node attributes, and `geom` edge attributes containing `LineString` geoms.</div> </div> +<div class="param-set"> + <div class="def"> + <div class="name">simplify_by_angle</div> + <div class="type">int</div> + </div> + <div class="desc"> + + The maximum angle to permit for a given edge. Angles greater than this will be reduced.</div> +</div> + +<div class="param-set"> + <div class="def"> + <div class="name">min_self_loop_length</div> + <div class="type">int</div> + </div> + <div class="desc"> + + Maximum self loop length to permit for a given edge.</div> +</div> + ### Returns <div class="param-set"> <div class="def"> @@ -578,9 +608,9 @@ side-effects as a function of varied node intensities when computing network cen See the guide on [graph cleaning](/guide#graph-cleaning) for more information. - _The pre-consolidation OSM street network for Soho, London. © OpenStreetMap contributors._ + _The pre-consolidation OSM street network for Soho, London. © OpenStreetMap contributors._ - _The consolidated OSM street network for Soho, London. © OpenStreetMap contributors._ + _The consolidated OSM street network for Soho, London. © OpenStreetMap contributors._ </div> diff --git a/docs/src/pages/tools/util.md b/docs/src/pages/tools/util.md index 3333dd6a..e211496a 100644 --- a/docs/src/pages/tools/util.md +++ b/docs/src/pages/tools/util.md @@ -664,6 +664,7 @@ layout: ../../layouts/PageLayout.astro <span class="pt">)->[</span> <span class="pr">STRtree</span> <span class="pr">list[dict[str</span> + <span class="pr">Any]]</span> <span class="pt">]</span> </div> </div> @@ -688,6 +689,7 @@ layout: ../../layouts/PageLayout.astro <span class="pt">)->[</span> <span class="pr">STRtree</span> <span class="pr">list[dict[str</span> + <span class="pr">Any]]</span> <span class="pt">]</span> </div> </div> diff --git a/pyproject.toml b/pyproject.toml index 1abc250a..745c5449 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "cityseer" -version = '4.16.4' +version = '4.16.5' description = "Computational tools for network-based pedestrian-scale urban analysis" readme = "README.md" requires-python = ">=3.10, <3.14" diff --git a/pysrc/cityseer/tools/graphs.py b/pysrc/cityseer/tools/graphs.py index c29ed965..6b73bfe8 100644 --- a/pysrc/cityseer/tools/graphs.py +++ b/pysrc/cityseer/tools/graphs.py @@ -515,7 +515,7 @@ def nx_snap_endpoints(nx_multigraph: MultiGraph) -> MultiGraph: def nx_iron_edges( - nx_multigraph: MultiGraph, + nx_multigraph: MultiGraph, simplify_by_angle: int = 100, min_self_loop_length: int = 100 ) -> MultiGraph: """ Simplifies edges. @@ -525,6 +525,10 @@ def nx_iron_edges( nx_multigraph: MultiGraph A `networkX` `MultiGraph` in a projected coordinate system, containing `x` and `y` node attributes, and `geom` edge attributes containing `LineString` geoms. + simplify_by_angle: int + The maximum angle to permit for a given edge. Angles greater than this will be reduced. + min_self_loop_length: int + Maximum self loop length to permit for a given edge. Returns ------- @@ -542,15 +546,16 @@ def nx_iron_edges( for start_nd_key, end_nd_key, edge_idx, edge_data in tqdm( g_multi_copy.edges(keys=True, data=True), disable=config.QUIET_MODE ): + edge_geom: geometry.LineString = edge_data["geom"] # only apply to non looping geoms otherwise issues occur - if start_nd_key == end_nd_key: + if start_nd_key == end_nd_key and edge_geom.length < min_self_loop_length: + remove_edges.append((start_nd_key, end_nd_key, edge_idx)) continue - edge_geom: geometry.LineString = edge_data["geom"] hits = edges_tree.query(edge_geom, predicate="crosses") if len(hits): remove_edges.append((start_nd_key, end_nd_key, edge_idx)) continue - line_coords = simplify_line_by_angle(edge_geom.coords, 100) + line_coords = simplify_line_by_angle(edge_geom.coords, simplify_by_angle) g_multi_copy[start_nd_key][end_nd_key][edge_idx]["geom"] = geometry.LineString(line_coords) g_multi_copy.remove_edges_from(remove_edges) # straightening parallel edges can create duplicates @@ -746,6 +751,9 @@ def _squash_adjacent( new_edge_geom = geometry.LineString(line_coords) if new_edge_geom.length == 0: continue + # bail if short self loop + if new_nd_name == target_nd_key and new_edge_geom.length < 100: + continue # check that a duplicate is not being added dupe = False if nx_multigraph.has_edge(new_nd_name, target_nd_key): @@ -856,10 +864,10 @@ def nx_consolidate_nodes( -------- See the guide on [graph cleaning](/guide#graph-cleaning) for more information. -  +  _The pre-consolidation OSM street network for Soho, London. © OpenStreetMap contributors._ -  +  _The consolidated OSM street network for Soho, London. © OpenStreetMap contributors._ """ diff --git a/pysrc/cityseer/tools/io.py b/pysrc/cityseer/tools/io.py index 83b4b02c..4fce9901 100644 --- a/pysrc/cityseer/tools/io.py +++ b/pysrc/cityseer/tools/io.py @@ -441,8 +441,8 @@ def osm_graph_from_poly( graph_crs = graphs.nx_remove_dangling_nodes(graph_crs, despine=0, remove_disconnected=remove_disconnected) # clean by highway types - leave motorway and trunk as is for dist, tags, simplify_line_angles in ( - (20, ["primary"], 45), # , "primary_link" - (18, ["primary", "secondary"], 45), # , "secondary_link" + (24, ["primary"], 45), # , "primary_link" + (20, ["primary", "secondary"], 45), # , "secondary_link" (16, ["primary", "secondary", "tertiary"], 45), # , "tertiary_link" ): graph_crs = graphs.nx_split_opposing_geoms( @@ -453,20 +453,22 @@ def osm_graph_from_poly( osm_hwy_target_tags=tags, prioritise_by_hwy_tag=True, simplify_line_angles=simplify_line_angles, + contains_buffer_dist=50, ) for dist, tags, simplify_line_angles in ( - (20, ["primary"], 95), # , "primary_link" - (18, ["primary", "secondary"], 95), # , "secondary_link" + (24, ["primary"], 95), # , "primary_link" + (20, ["primary", "secondary"], 95), # , "secondary_link" (16, ["primary", "secondary", "tertiary"], 95), # , "tertiary_link" ): graph_crs = graphs.nx_consolidate_nodes( graph_crs, buffer_dist=dist, - crawl=True, + crawl=False, centroid_by_itx=True, osm_hwy_target_tags=tags, prioritise_by_hwy_tag=True, simplify_line_angles=simplify_line_angles, + contains_buffer_dist=50, ) graph_crs = graphs.nx_remove_filler_nodes(graph_crs) # do smaller scale cleaning @@ -479,6 +481,7 @@ def osm_graph_from_poly( "cycleway", "bridleway", "footway", + "footway_pedestrian", # plazas "path", "living_street", "unclassified", @@ -495,6 +498,7 @@ def osm_graph_from_poly( osm_hwy_target_tags=tags, prioritise_by_hwy_tag=True, simplify_line_angles=simplify_angles, + contains_buffer_dist=50, ) graph_crs = graphs.nx_consolidate_nodes( graph_crs, @@ -504,6 +508,7 @@ def osm_graph_from_poly( osm_hwy_target_tags=tags, prioritise_by_hwy_tag=True, simplify_line_angles=simplify_angles, + contains_buffer_dist=50, ) graph_crs = graphs.nx_remove_filler_nodes(graph_crs) # snap gapped endings - don't clean danglers before this @@ -517,6 +522,7 @@ def osm_graph_from_poly( "cycleway", "bridleway", "footway", + "footway_pedestrian", # plazas "path", "living_street", "unclassified", @@ -532,7 +538,7 @@ def osm_graph_from_poly( squash_nodes=False, ) # remove longer danglers - graph_crs = graphs.nx_remove_dangling_nodes(graph_crs, despine=20) + graph_crs = graphs.nx_remove_dangling_nodes(graph_crs, despine=50) return graph_crs