From b5c56a02dc012387ddac633de04e6551199e5438 Mon Sep 17 00:00:00 2001 From: Daniel Himmelstein Date: Sun, 3 Mar 2024 10:41:32 -0500 Subject: [PATCH] plot_orientation: support directed graph bearings refs https://github.com/gboeing/osmnx/issues/1137 --- osmnx/bearing.py | 21 ++++++++++++++++----- osmnx/plot.py | 9 ++++++--- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/osmnx/bearing.py b/osmnx/bearing.py index 0dbc2211b..2b109465a 100644 --- a/osmnx/bearing.py +++ b/osmnx/bearing.py @@ -3,6 +3,7 @@ from __future__ import annotations from typing import overload +from warnings import warn import networkx as nx import numpy as np @@ -169,7 +170,7 @@ def orientation_entropy( def _extract_edge_bearings( - Gu: nx.MultiGraph, + Gu: nx.MultiGraph | nx.MultiDiGraph, min_length: float, weight: str | None, ) -> npt.NDArray[np.float64]: @@ -198,7 +199,7 @@ def _extract_edge_bearings( The bidirectional edge bearings of `Gu`. """ if nx.is_directed(Gu) or projection.is_projected(Gu.graph["crs"]): # pragma: no cover - msg = "Graph must be undirected and unprojected to analyze edge bearings." + msg = "Graph must be unprojected to analyze edge bearings." raise ValueError(msg) bearings = [] for u, v, data in Gu.edges(data=True): @@ -211,15 +212,25 @@ def _extract_edge_bearings( # don't weight bearings, just take one value per edge bearings.append(data["bearing"]) - # drop any nulls, calculate reverse bearings, concatenate and return + # drop any nulls bearings_array = np.array(bearings) bearings_array = bearings_array[~np.isnan(bearings_array)] + if nx.is_directed(Gu): + # https://github.com/gboeing/osmnx/issues/1137 + msg = ( + "Extracting directional bearings (one bearing per edge) due to MultiDiGraph input. " + "To extract bidirectional bearings (two bearings per edge, including the reverse bearing), " + "supply an undirected graph instead via `Gu.to_undirected()`." + ) + warn(msg, category=UserWarning, stacklevel=2) + return bearings_array + # for undirected graphs, add reverse bearings and return bearings_array_r = (bearings_array - 180) % 360 return np.concatenate([bearings_array, bearings_array_r]) def _bearings_distribution( - Gu: nx.MultiGraph, + Gu: nx.MultiGraph | nx.MultiDiGraph, num_bins: int, min_length: float, weight: str | None, @@ -236,7 +247,7 @@ def _bearings_distribution( Parameters ---------- Gu - Undirected, unprojected graph with `bearing` attributes on each edge. + Unprojected graph with `bearing` attributes on each edge. num_bins Number of bins for the bearing histogram. min_length diff --git a/osmnx/plot.py b/osmnx/plot.py index e4be2fd6a..9d4914288 100644 --- a/osmnx/plot.py +++ b/osmnx/plot.py @@ -664,7 +664,7 @@ def plot_footprints( # noqa: PLR0913 def plot_orientation( # noqa: PLR0913 - Gu: nx.MultiGraph, + Gu: nx.MultiGraph | nx.MultiDiGraph, *, num_bins: int = 36, min_length: float = 0, @@ -682,7 +682,10 @@ def plot_orientation( # noqa: PLR0913 xtick_font: dict[str, Any] | None = None, ) -> tuple[Figure, PolarAxes]: """ - Plot a polar histogram of a spatial network's bidirectional edge bearings. + Plot a polar histogram of a spatial network's edge bearings. + + A MultiGraph input receives bidirectional bearings, while a MultiDiGraph + input receives directional bearings (one bearing per edge). Ignores self-loop edges as their bearings are undefined. See also the `bearings` module. @@ -694,7 +697,7 @@ def plot_orientation( # noqa: PLR0913 Parameters ---------- Gu - Undirected, unprojected graph with `bearing` attributes on each edge. + Unprojected graph with `bearing` attributes on each edge. num_bins Number of bins. For example, if `num_bins=36` is provided, then each bin will represent 10 degrees around the compass.