def add_edge_bearings(G: nx.MultiDiGraph) -> nx.MultiDiGraph:
    """
    Calculate and add compass `bearing` attributes to all graph edges.

    Vectorized function to calculate (initial) bearing from origin node to
    destination node for each edge in a directed, unprojected graph then add
    these bearings as new `bearing` edge attributes. Bearing represents angle
    in degrees (clockwise) between north and the geodesic line from the origin
    node to the destination node. Ignores self-loop edges as their bearings
    are undefined.

    Parameters
    ----------
    G
        Unprojected graph.

    Returns
    -------
    G
        Graph with `bearing` attributes on the edges.
    """
    if projection.is_projected(G.graph["crs"]):  # pragma: no cover
        msg = "Graph must be unprojected to add edge bearings."
        raise ValueError(msg)

    # extract edge IDs and corresponding coordinates from their nodes
    uvk = [(u, v, k) for u, v, k in G.edges if u != v]
    x = G.nodes(data="x")
    y = G.nodes(data="y")
    coords = np.array([(y[u], x[u], y[v], x[v]) for u, v, k in uvk])

    # calculate bearings then set as edge attributes
    bearings = calculate_bearing(coords[:, 0], coords[:, 1], coords[:, 2], coords[:, 3])
    values = zip(uvk, bearings)
    nx.set_edge_attributes(G, dict(values), name="bearing")

    return G

    Calculate and add compass `bearing` attributes to all graph edges.

    Vectorized function to calculate (initial) bearing from origin node to
    destination node for each edge in a directed, unprojected graph then add
    these bearings as new `bearing` edge attributes. Bearing represents angle
    in degrees (clockwise) between north and the geodesic line from the origin
    node to the destination node. Ignores self-loop edges as their bearings
    are undefined.

    Parameters
    ----------
    G
        Unprojected graph.

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