diff --git a/docs/src/pages/rustalgos/rustalgos.md b/docs/src/pages/rustalgos/rustalgos.md index 34d0b029..9c1c29c3 100644 --- a/docs/src/pages/rustalgos/rustalgos.md +++ b/docs/src/pages/rustalgos/rustalgos.md @@ -2726,17 +2726,17 @@ datapoints are not located with high spatial precision. -node_xys: list[tuple[float, float]] +node_xs: list[float] -node_xs: list[float] +node_ys: list[float] -node_ys: list[float] +node_xys: list[tuple[float, float]] diff --git a/docs/src/pages/tools/graphs.md b/docs/src/pages/tools/graphs.md index 3e52cb8f..14de5245 100644 --- a/docs/src/pages/tools/graphs.md +++ b/docs/src/pages/tools/graphs.md @@ -407,7 +407,7 @@ side-effects as a function of varied node intensities when computing network cen
contains_buffer_dist : - int = 40 + int = 25
) @@ -541,7 +541,7 @@ side-effects as a function of varied node intensities when computing network cen
contains_buffer_dist : - float = 40 + float = 25
) diff --git a/docs/src/pages/tools/io.md b/docs/src/pages/tools/io.md index 5ef167aa..b32a8936 100644 --- a/docs/src/pages/tools/io.md +++ b/docs/src/pages/tools/io.md @@ -373,6 +373,21 @@ builds a graph automatically. : bool = True +
+ crawl_consolidate_dist + : + int = 12 +
+
+ parallel_consolidate_dist + : + int = 15 +
+
+ iron_edges + : + bool = True +
timeout : @@ -452,6 +467,36 @@ builds a graph automatically. Whether to automatically simplify the OSM graph. Set to False for manual cleaning.
+
+
+
crawl_consolidate_dist
+
int
+
+
+ + The buffer distance to use when doing the initial round of node consolidation. This consolidation step crawls neighbouring nodes to find groups of adjacent nodes within the buffer distance of each other.
+
+ +
+
+
parallel_consolidate_dist
+
int
+
+
+ + The buffer distance to use when looking for adjacent parallel roadways.
+
+ +
+
+
iron_edges
+
bool
+
+
+ + Whether to iron the edges.
+
+
timeout
diff --git a/pyproject.toml b/pyproject.toml index 7e4aa727..ecf590de 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "cityseer" -version = '4.3.4' +version = '4.4.0' description = "Computational tools for network-based pedestrian-scale urban analysis" readme = "README.md" requires-python = ">=3.10, <3.12" diff --git a/pysrc/cityseer/tools/graphs.py b/pysrc/cityseer/tools/graphs.py index cde143a2..b4f5b12d 100644 --- a/pysrc/cityseer/tools/graphs.py +++ b/pysrc/cityseer/tools/graphs.py @@ -27,7 +27,7 @@ logger = logging.getLogger(__name__) -def nx_simple_geoms(nx_multigraph: MultiGraph, simplify_dist: int = 5) -> MultiGraph: +def nx_simple_geoms(nx_multigraph: MultiGraph) -> MultiGraph: """ Inferring geometries from node to node. @@ -38,8 +38,6 @@ def nx_simple_geoms(nx_multigraph: MultiGraph, simplify_dist: int = 5) -> MultiG ---------- nx_multigraph: MultiGraph A `networkX` `MultiGraph` with `x` and `y` node attributes. - simplify_dist: int - Simplification distance to use for simplifying the linestring geometries. Returns ------- @@ -75,7 +73,6 @@ def _process_node(nd_key: NodeKey): s_x, s_y = _process_node(start_nd_key) e_x, e_y = _process_node(end_nd_key) seg = geometry.LineString([[s_x, s_y], [e_x, e_y]]) - seg: geometry.LineString = seg.simplify(simplify_dist) if start_nd_key == end_nd_key and seg.length == 0: remove_edges.append((start_nd_key, end_nd_key, edge_idx)) else: @@ -468,31 +465,25 @@ def nx_iron_edges( if start_nd_key == end_nd_key: continue edge_geom: geometry.LineString = edge_data["geom"] - start_point = edge_geom.coords[0] - end_point = edge_geom.coords[-1] - simpl_edge_geom = geometry.LineString([start_point, end_point]) - max_angle = util.measure_max_angle(edge_geom.coords) total_angle = util.measure_cumulative_angle(edge_geom.coords) - # for all changes - write over edge_geom and also update in place - # remove erroneous switchbacks - if max_angle > 100: - edge_geom = simpl_edge_geom - # remove line geoms where cumulative length is greater than around 1.5 * shortest route (hypotenuse) - elif edge_geom.length > simpl_edge_geom.length * 1.8: - edge_geom = simpl_edge_geom - elif simpl_edge_geom.buffer(15).contains(edge_geom): - edge_geom = simpl_edge_geom - elif edge_geom.length < 50: - edge_geom = simpl_edge_geom - # subtle simplication - if total_angle < 5: - edge_geom = edge_geom.simplify(20, preserve_topology=False) - elif total_angle < 10: - edge_geom = edge_geom.simplify(10, preserve_topology=False) - elif total_angle < 20: - edge_geom = edge_geom.simplify(5, preserve_topology=False) + max_angle = util.measure_max_angle(edge_geom.coords) + # don't apply to longer geoms, e.g. rural roads + if edge_geom.length > 100: + edge_geom = edge_geom.simplify(4) + # if there is an angle greater than 95 then it is likely spurious + elif max_angle > 100: + edge_geom = edge_geom.simplify(16) + # preserve resolution for twisty roads + elif total_angle > 170: + edge_geom = edge_geom.simplify(4) + # look for spurious kinks + elif edge_geom.simplify(16).buffer(16).contains(edge_geom) and max_angle > 35: + edge_geom = edge_geom.simplify(16) + elif edge_geom.simplify(8).buffer(8).contains(edge_geom) and max_angle > 35: + edge_geom = edge_geom.simplify(8) else: - edge_geom = edge_geom.simplify(2, preserve_topology=True) + edge_geom = edge_geom.simplify(4) + g_multi_copy[start_nd_key][end_nd_key][edge_idx]["geom"] = edge_geom # straightening parallel edges can create duplicates g_multi_copy = nx_merge_parallel_edges(g_multi_copy, False, 1) @@ -652,7 +643,7 @@ def nx_consolidate_nodes( crawl: bool = False, centroid_by_itx: bool = True, merge_edges_by_midline: bool = True, - contains_buffer_dist: int = 40, + contains_buffer_dist: int = 25, ) -> MultiGraph: """ Consolidates nodes if they are within a buffer distance of each other. @@ -805,7 +796,7 @@ def nx_split_opposing_geoms( nx_multigraph: MultiGraph, buffer_dist: float = 12, merge_edges_by_midline: bool = True, - contains_buffer_dist: float = 40, + contains_buffer_dist: float = 25, ) -> MultiGraph: """ Split edges opposite nodes on parallel edge segments if within a buffer distance. diff --git a/pysrc/cityseer/tools/io.py b/pysrc/cityseer/tools/io.py index 15b707d4..0180fa9c 100644 --- a/pysrc/cityseer/tools/io.py +++ b/pysrc/cityseer/tools/io.py @@ -247,6 +247,9 @@ def osm_graph_from_poly( to_epsg_code: int | None = None, custom_request: str | None = None, simplify: bool = True, + crawl_consolidate_dist: int = 12, + parallel_consolidate_dist: int = 15, + iron_edges: bool = True, timeout: int = 300, max_tries: int = 3, ) -> nx.MultiGraph: # noqa @@ -274,6 +277,13 @@ def osm_graph_from_poly( the geometry passed to the OSM API query. See the discussion below. simplify: bool Whether to automatically simplify the OSM graph. Set to False for manual cleaning. + crawl_consolidate_dist: int + The buffer distance to use when doing the initial round of node consolidation. This consolidation step crawls + neighbouring nodes to find groups of adjacent nodes within the buffer distance of each other. + parallel_consolidate_dist: int + The buffer distance to use when looking for adjacent parallel roadways. + iron_edges: bool + Whether to iron the edges. timeout: int Timeout duration for API call in seconds. max_tries: int @@ -365,10 +375,11 @@ def osm_graph_from_poly( graph_crs = graphs.nx_remove_filler_nodes(graph_crs) if simplify: graph_crs = graphs.nx_remove_dangling_nodes(graph_crs) - graph_crs = graphs.nx_consolidate_nodes(graph_crs, buffer_dist=12, crawl=True) - graph_crs = graphs.nx_split_opposing_geoms(graph_crs, buffer_dist=15) - graph_crs = graphs.nx_consolidate_nodes(graph_crs, buffer_dist=15, neighbour_policy="indirect") + graph_crs = graphs.nx_consolidate_nodes(graph_crs, buffer_dist=crawl_consolidate_dist, crawl=True) + graph_crs = graphs.nx_split_opposing_geoms(graph_crs, buffer_dist=parallel_consolidate_dist) + graph_crs = graphs.nx_consolidate_nodes(graph_crs, buffer_dist=parallel_consolidate_dist) graph_crs = graphs.nx_remove_filler_nodes(graph_crs) + if iron_edges: graph_crs = graphs.nx_iron_edges(graph_crs) return graph_crs