Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add node-specific tolerances to intersection consolidation #1160

Merged
merged 18 commits into from
Apr 25, 2024
Merged
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 31 additions & 8 deletions osmnx/simplification.py
Original file line number Diff line number Diff line change
Expand Up @@ -446,7 +446,7 @@
def consolidate_intersections(
G: nx.MultiDiGraph,
*,
tolerance: float = 10,
tolerance: float | dict[int, float] = 10,
rebuild_graph: bool = True,
dead_ends: bool = False,
reconnect_edges: bool = True,
Expand All @@ -463,6 +463,11 @@
Note `tolerance` represents a per-node buffering radius: for example, to
consolidate nodes within 10 meters of each other, use `tolerance=5`.

It's also possible to specify difference tolerances for each node. This can
be done by adding an attribute to each node with contains the tolerance, and
passing the name of that argument as tolerance_attribute argument. If a node
gboeing marked this conversation as resolved.
Show resolved Hide resolved
does not have a value in the tolerance_attribute, the default tolerance is used.

When `rebuild_graph` is False, it uses a purely geometric (and relatively
fast) algorithm to identify "geometrically close" nodes, merge them, and
return the merged intersections' centroids. When `rebuild_graph` is True,
Expand All @@ -487,7 +492,8 @@
A projected graph.
tolerance
Nodes are buffered to this distance (in graph's geometry's units) and
subsequent overlaps are dissolved into a single node.
subsequent overlaps are dissolved into a single node. Can be a float
value or a dictionary mapping node IDs to individual tolerance values.
rebuild_graph
If True, consolidate the nodes topologically, rebuild the graph, and
return as MultiDiGraph. Otherwise, consolidate the nodes geometrically
Expand Down Expand Up @@ -547,7 +553,10 @@
return _merge_nodes_geometric(G, tolerance).centroid


def _merge_nodes_geometric(G: nx.MultiDiGraph, tolerance: float) -> gpd.GeoSeries:
def _merge_nodes_geometric(
G: nx.MultiDiGraph,
tolerance: float | dict[int, float],
) -> gpd.GeoSeries:
"""
Geometrically merge nodes within some distance of each other.

Expand All @@ -558,14 +567,25 @@
tolerance
Buffer nodes to this distance (in graph's geometry's units) then merge
overlapping polygons into a single polygon via unary union operation.
Can be a float value or a dictionary mapping node IDs to individual
tolerance values.

Returns
-------
merged
The merged overlapping polygons of the buffered nodes.
"""
# buffer nodes GeoSeries then get unary union to merge overlaps
merged = convert.graph_to_gdfs(G, edges=False)["geometry"].buffer(tolerance).unary_union
gdf_nodes = convert.graph_to_gdfs(G, edges=False)

if isinstance(tolerance, dict):
# Map tolerances to node IDs, using NaN for nodes not in the dictionary, and fill NaN with zero
buffer_distances = gdf_nodes.index.to_series().map(tolerance).fillna(0)

Check warning on line 582 in osmnx/simplification.py

View check run for this annotation

Codecov / codecov/patch

osmnx/simplification.py#L582

Added line #L582 was not covered by tests
gboeing marked this conversation as resolved.
Show resolved Hide resolved
else:
# Use the default tolerance for all nodes
buffer_distances = tolerance

# Buffer nodes to the specified distances and merge them
merged = gdf_nodes.geometry.buffer(distance=buffer_distances).unary_union

# if only a single node results, make it iterable to convert to GeoSeries
merged = MultiPolygon([merged]) if isinstance(merged, Polygon) else merged
gboeing marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -574,7 +594,7 @@

def _consolidate_intersections_rebuild_graph( # noqa: C901,PLR0912,PLR0915
G: nx.MultiDiGraph,
tolerance: float,
tolerance: float | dict[int, float],
reconnect_edges: bool, # noqa: FBT001
node_attr_aggs: dict[str, Any] | None,
) -> nx.MultiDiGraph:
Expand All @@ -599,7 +619,8 @@
A projected graph.
tolerance
Nodes are buffered to this distance (in graph's geometry's units) and
subsequent overlaps are dissolved into a single node.
subsequent overlaps are dissolved into a single node. Can be a float
value or a dictionary mapping node IDs to individual tolerance values.
reconnect_edges
If True, reconnect edges (and their geometries) to the consolidated
nodes in rebuilt graph, and update the edge length attributes. If
Expand All @@ -625,7 +646,9 @@
# STEP 1
# buffer nodes to passed-in distance and merge overlaps. turn merged nodes
# into gdf and get centroids of each cluster as x, y
node_clusters = gpd.GeoDataFrame(geometry=_merge_nodes_geometric(G, tolerance))
node_clusters = gpd.GeoDataFrame(
geometry=_merge_nodes_geometric(G, tolerance),
)
centroids = node_clusters.centroid
node_clusters["x"] = centroids.x
node_clusters["y"] = centroids.y
Expand Down