def add_edge_lengths(
    G: nx.MultiDiGraph,
    *,
    edges: Iterable[tuple[int, int, int]] | None = None,
) -> nx.MultiDiGraph:
    """
    Calculate and add `length` attribute (in meters) to each edge.

    Vectorized function to calculate great-circle distance between each edge's
    incident nodes. Ensure graph is unprojected and unsimplified to calculate
    accurate distances.

    Note: this function is run by all the `graph.graph_from_x` functions
    automatically to add `length` attributes to all edges. It calculates edge
    lengths as the great-circle distance from node `u` to node `v`. When
    OSMnx automatically runs this function upon graph creation, it does it
    before simplifying the graph: thus it calculates the straight-line lengths
    of edge segments that are themselves all straight. Only after
    simplification do edges take on (potentially) curvilinear geometry. If you
    wish to calculate edge lengths later, note that you will be calculating
    straight-line distances which necessarily ignore the curvilinear geometry.
    Thus you only want to run this function on a graph with all straight edges
    (such as is the case with an unsimplified graph).

    Parameters
    ----------
    G
        Unprojected and unsimplified input graph.
    edges
        The subset of edges to add `length` attributes to, as `(u, v, k)`
        tuples. If None, add lengths to all edges.

    Returns
    -------
    G
        Graph with `length` attributes on the edges.
    """
    uvk = G.edges if edges is None else edges

    # extract edge IDs and corresponding coordinates from their nodes
    x = G.nodes(data="x")
    y = G.nodes(data="y")
    msg = "Some edges missing nodes, possibly due to input data clipping issue."
    try:
        # two-dimensional array of coordinates: y0, x0, y1, x1
        c = np.array([(y[u], x[u], y[v], x[v]) for u, v, k in uvk])
    except KeyError as e:  # pragma: no cover
        raise ValueError(msg) from e
    else:
        # ensure all coordinates can be converted to float and are non-null
        if np.isnan(c.astype(float)).any():
            raise ValueError(msg)

    # calculate great circle distances, round, and fill nulls with zeros
    dists = great_circle(c[:, 0], c[:, 1], c[:, 2], c[:, 3])
    dists[np.isnan(dists)] = 0
    nx.set_edge_attributes(G, values=dict(zip(uvk, dists)), name="length")

    msg = "Added length attributes to graph edges"
    utils.log(msg, level=lg.INFO)
    return G

    Calculate and add `length` attribute (in meters) to each edge.

    Vectorized function to calculate great-circle distance between each edge's
    incident nodes. Ensure graph is unprojected and unsimplified to calculate
    accurate distances.

    Note: this function is run by all the `graph.graph_from_x` functions
    automatically to add `length` attributes to all edges. It calculates edge
    lengths as the great-circle distance from node `u` to node `v`. When
    OSMnx automatically runs this function upon graph creation, it does it
    before simplifying the graph: thus it calculates the straight-line lengths
    of edge segments that are themselves all straight. Only after
    simplification do edges take on (potentially) curvilinear geometry. If you
    wish to calculate edge lengths later, note that you will be calculating
    straight-line distances which necessarily ignore the curvilinear geometry.
    Thus you only want to run this function on a graph with all straight edges
    (such as is the case with an unsimplified graph).

    Parameters
    ----------
    G
        Unprojected and unsimplified input graph.
    edges
        The subset of edges to add `length` attributes to, as `(u, v, k)`
        tuples. If None, add lengths to all edges.

    Returns
    -------
    G
        Graph with `length` attributes on the edges.
    
osmnx/distance.py