def consolidate_intersections(
    G: nx.MultiDiGraph,
    *,
    tolerance: float | dict[int, float] = 10,
    rebuild_graph: bool = True,
    dead_ends: bool = False,
    reconnect_edges: bool = True,
    node_attr_aggs: dict[str, Any] | None = None,
) -> nx.MultiDiGraph | gpd.GeoSeries:
    """
    Consolidate intersections comprising clusters of nearby nodes.

    This algorithm is described in the journal article: Boeing, G. 2025.
    "Topological Graph Simplification Solutions to the Street Intersection
    Miscount Problem." Transactions in GIS, 29 (3), e70037.
    https://doi.org/10.1111/tgis.70037

    Merges nearby nodes and returns either their centroids or a rebuilt graph
    with consolidated intersections and reconnected edge geometries. The
    `tolerance` argument can be a single value applied to all nodes or
    individual per-node values. It should be adjusted to approximately match
    street design standards in the specific street network, and you should use
    a projected graph to work in meaningful and consistent units like meters.
    Note: `tolerance` represents a per-node buffering radius. For example, to
    consolidate nodes within 10 meters of each other, use `tolerance=5`.

    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,
    it uses a topological (and slower but more accurate) algorithm to identify
    "topologically close" nodes, merge them, then rebuild/return the graph.
    Returned graph's node IDs represent clusters rather than "osmid" values.
    Refer to nodes' "osmid_original" attributes for original "osmid" values.
    If multiple nodes were merged together, the "osmid_original" attribute is
    a list of merged nodes' "osmid" values.

    Divided roads are often represented by separate centerline edges. The
    intersection of two divided roads thus creates 4 nodes, representing where
    each edge intersects a perpendicular edge. These 4 nodes represent a
    single intersection in the real world. A similar situation occurs with
    roundabouts and traffic circles. This function consolidates nearby nodes
    by buffering them to an arbitrary distance, merging overlapping buffers,
    and taking their centroid.

    Parameters
    ----------
    G
        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. If scalar, then
        that single value will be used for all nodes. If dict (mapping node
        IDs to individual values), then those values will be used per node and
        any missing node IDs will not be buffered.
    rebuild_graph
        If True, consolidate the nodes topologically, rebuild the graph, and
        return as MultiDiGraph. Otherwise, consolidate the nodes geometrically
        and return the consolidated node points as GeoSeries.
    dead_ends
        If False, discard dead-end nodes to return only street-intersection
        points.
    reconnect_edges
        If True, reconnect edges (and their geometries) to the consolidated
        nodes in rebuilt graph, and update the edge length attributes. If
        False, the returned graph has no edges (which is faster if you just
        need topologically consolidated intersection counts). Ignored if
        `rebuild_graph` is not True.
    node_attr_aggs
        Allows user to aggregate node attributes values when merging nodes.
        Keys are node attribute names and values are aggregation functions
        (anything accepted as an argument by `pandas.agg`). Node attributes
        not in `node_attr_aggs` will contain the unique values across the
        merged nodes. If None, defaults to `{"elevation": numpy.mean}`.

    Returns
    -------
    Gc or gs
        If `rebuild_graph=True`, returns MultiDiGraph with consolidated
        intersections and (optionally) reconnected edge geometries. If
        `rebuild_graph=False`, returns GeoSeries of Points representing the
        centroids of street intersections.
    """
    # make a copy to not mutate original graph object caller passed in
    G = G.copy()

    # if dead_ends is False, discard dead-ends to retain only intersections
    if not dead_ends:
        spn = stats.streets_per_node(G)
        dead_end_nodes = [node for node, count in spn.items() if count <= 1]
        G.remove_nodes_from(dead_end_nodes)

    if rebuild_graph:
        if len(G.nodes) == 0 or len(G.edges) == 0:
            # cannot rebuild a graph with no nodes or no edges, just return it
            return G

        # otherwise
        return _consolidate_intersections_rebuild_graph(
            G,
            tolerance,
            reconnect_edges,
            node_attr_aggs,
        )

    # otherwise, if we're not rebuilding the graph
    if len(G) == 0:
        # if graph has no nodes, just return empty GeoSeries
        return gpd.GeoSeries(crs=G.graph["crs"])

    # otherwise, return the centroids of the merged intersection polygons
    return _merge_nodes_geometric(G, tolerance).centroid

    Consolidate intersections comprising clusters of nearby nodes.

    This algorithm is described in the journal article: Boeing, G. 2025.
    "Topological Graph Simplification Solutions to the Street Intersection
    Miscount Problem." Transactions in GIS, 29 (3), e70037.
    https://doi.org/10.1111/tgis.70037

    Merges nearby nodes and returns either their centroids or a rebuilt graph
    with consolidated intersections and reconnected edge geometries. The
    `tolerance` argument can be a single value applied to all nodes or
    individual per-node values. It should be adjusted to approximately match
    street design standards in the specific street network, and you should use
    a projected graph to work in meaningful and consistent units like meters.
    Note: `tolerance` represents a per-node buffering radius. For example, to
    consolidate nodes within 10 meters of each other, use `tolerance=5`.

    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,
    it uses a topological (and slower but more accurate) algorithm to identify
    "topologically close" nodes, merge them, then rebuild/return the graph.
    Returned graph's node IDs represent clusters rather than "osmid" values.
    Refer to nodes' "osmid_original" attributes for original "osmid" values.
    If multiple nodes were merged together, the "osmid_original" attribute is
    a list of merged nodes' "osmid" values.

    Divided roads are often represented by separate centerline edges. The
    intersection of two divided roads thus creates 4 nodes, representing where
    each edge intersects a perpendicular edge. These 4 nodes represent a
    single intersection in the real world. A similar situation occurs with
    roundabouts and traffic circles. This function consolidates nearby nodes
    by buffering them to an arbitrary distance, merging overlapping buffers,
    and taking their centroid.

    Parameters
    ----------
    G
        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. If scalar, then
        that single value will be used for all nodes. If dict (mapping node
        IDs to individual values), then those values will be used per node and
        any missing node IDs will not be buffered.
    rebuild_graph
        If True, consolidate the nodes topologically, rebuild the graph, and
        return as MultiDiGraph. Otherwise, consolidate the nodes geometrically
        and return the consolidated node points as GeoSeries.
    dead_ends
        If False, discard dead-end nodes to return only street-intersection
        points.
    reconnect_edges
        If True, reconnect edges (and their geometries) to the consolidated
        nodes in rebuilt graph, and update the edge length attributes. If
        False, the returned graph has no edges (which is faster if you just
        need topologically consolidated intersection counts). Ignored if
        `rebuild_graph` is not True.
    node_attr_aggs
        Allows user to aggregate node attributes values when merging nodes.
        Keys are node attribute names and values are aggregation functions
        (anything accepted as an argument by `pandas.agg`). Node attributes
        not in `node_attr_aggs` will contain the unique values across the
        merged nodes. If None, defaults to `{"elevation": numpy.mean}`.

    Returns
    -------
    Gc or gs
        If `rebuild_graph=True`, returns MultiDiGraph with consolidated
        intersections and (optionally) reconnected edge geometries. If
        `rebuild_graph=False`, returns GeoSeries of Points representing the
        centroids of street intersections.
    
osmnx/simplification.py