Skip to content

Commit

Permalink
minor changes to #1139
Browse files Browse the repository at this point in the history
  • Loading branch information
gboeing committed Mar 6, 2024
1 parent bc0a9bc commit ea397d3
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 36 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Read the v2 [migration guide](https://github.com/gboeing/osmnx/issues/1123)
- make which_result function parameter consistently able to accept a list throughout package (#1113)
- make utils_geo.bbox_from_point function return a tuple of floats for consistency with rest of package (#1113)
- change add_node_elevations_google default batch_size to 512 to match Google's limit (#1115)
- support analysis of directional edge bearings on MultiDiGraph input (#1137 #1139)
- allow analysis of MultiDiGraph directional edge bearings and orientation (#1139)
- fix bug in \_downloader.\_save_to_cache function usage (#1107)
- fix bug in handling requests ConnectionError when querying Overpass status endpoint (#1113)
- fix minor bugs throughout to address inconsistencies revealed by type enforcement (#1107 #1114)
Expand Down
33 changes: 17 additions & 16 deletions osmnx/bearing.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ def add_edge_bearings(G: nx.MultiDiGraph) -> nx.MultiDiGraph:


def orientation_entropy(
G: nx.MultiGraph,
G: nx.MultiGraph | nx.MultiDiGraph,
*,
num_bins: int = 36,
min_length: float = 0,
Expand All @@ -132,12 +132,12 @@ def orientation_entropy(
"""
Calculate graph's orientation entropy.
Orientation entropy is the Shannon entropy of the graphs' edges'
bearings across evenly spaced bins. Ignores self-loop edges
as their bearings are undefined.
For MultiGraph input, calculates entropy of bidirectional bearings.
For MultiDiGraph input, calculates entropy of directional bearings.
Orientation entropy is the Shannon entropy of the graphs' edges' bearings
across evenly spaced bins. Ignores self-loop edges as their bearings are
undefined. If `G` is a MultiGraph, all edge bearings will be bidirectional
(ie, two reciprocal bearings per undirected edge). If `G` is a
MultiDiGraph, all edge bearings will be directional (ie, one bearing per
directed edge).
For more info see: Boeing, G. 2019. "Urban Spatial Order: Street Network
Orientation, Configuration, and Entropy." Applied Network Science, 4 (1),
Expand Down Expand Up @@ -173,17 +173,19 @@ def orientation_entropy(


def _extract_edge_bearings(
G: nx.MultiGraph,
G: nx.MultiGraph | nx.MultiDiGraph,
min_length: float,
weight: str | None,
) -> npt.NDArray[np.float64]:
"""
Extract graph's edge bearings.
A MultiGraph input receives bidirectional bearings.
For example, if an undirected edge has a bearing of 90 degrees then we will record
Ignores self-loop edges as their bearings are undefined. If `G` is a
MultiGraph, all edge bearings will be bidirectional (ie, two reciprocal
bearings per undirected edge). If `G` is a MultiDiGraph, all edge bearings
will be directional (ie, one bearing per directed edge). For example, if
an undirected edge has a bearing of 90 degrees then we will record
bearings of both 90 degrees and 270 degrees for this edge.
For MultiDiGraph input, record only one bearing per edge.
Parameters
----------
Expand Down Expand Up @@ -221,11 +223,10 @@ def _extract_edge_bearings(
bearings_array = np.array(bearings)
bearings_array = bearings_array[~np.isnan(bearings_array)]
if nx.is_directed(G):
# 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 `osmnx.get_undirected(G)`."
"`G` is a MultiDiGraph, so edge bearings will be directional (one per "
"edge). If you want bidirectional edge bearings (two reciprocal bearings "
"per edge), pass a MultiGraph instead. Use `utils_graph.get_undirected`."
)
warn(msg, category=UserWarning, stacklevel=2)
return bearings_array
Expand All @@ -235,7 +236,7 @@ def _extract_edge_bearings(


def _bearings_distribution(
G: nx.MultiGraph,
G: nx.MultiGraph | nx.MultiDiGraph,
num_bins: int,
min_length: float,
weight: str | None,
Expand Down
10 changes: 5 additions & 5 deletions osmnx/plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -664,7 +664,7 @@ def plot_footprints( # noqa: PLR0913


def plot_orientation( # noqa: PLR0913
G: nx.MultiGraph,
G: nx.MultiGraph | nx.MultiDiGraph,
*,
num_bins: int = 36,
min_length: float = 0,
Expand All @@ -684,10 +684,10 @@ def plot_orientation( # noqa: PLR0913
"""
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
Ignores self-loop edges as their bearings are undefined. If `G` is a
MultiGraph, all edge bearings will be bidirectional (ie, two reciprocal
bearings per undirected edge). If `G` is a MultiDiGraph, all edge bearings
will be directional (ie, one bearing per directed edge). See also the
`bearings` module.
For more info see: Boeing, G. 2019. "Urban Spatial Order: Street Network
Expand Down
36 changes: 22 additions & 14 deletions tests/test_osmnx.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,6 @@ def test_stats() -> None:
# create graph, add a new node, add bearings, project it
G = ox.graph_from_place(place1, network_type="all")
G.add_node(0, x=location_point[1], y=location_point[0], street_count=0)
_ = ox.bearing.calculate_bearing(0, 0, 1, 1)
G = ox.add_edge_bearings(G)
G_proj = ox.project_graph(G)
G_proj = ox.distance.add_edge_lengths(G_proj, edges=tuple(G_proj.edges)[0:3])

Expand All @@ -118,13 +116,6 @@ def test_stats() -> None:
stats = ox.basic_stats(G, area=1000)
stats = ox.basic_stats(G_proj, area=1000, clean_int_tol=15)

# calculate entropy
Gu = ox.get_undirected(G)
entropy = ox.bearing.orientation_entropy(Gu, weight="length")

fig, ax = ox.plot.plot_orientation(Gu, area=True, title="Title")
fig, ax = ox.plot.plot_orientation(Gu, ax=ax, area=False, title="Title")

# test cleaning and rebuilding graph
G_clean = ox.consolidate_intersections(G_proj, tolerance=10, rebuild_graph=True, dead_ends=True)
G_clean = ox.consolidate_intersections(
Expand All @@ -141,18 +132,35 @@ def test_stats() -> None:
G_clean = ox.consolidate_intersections(G, rebuild_graph=False)


def test_extract_edge_bearings_directionality() -> None:
"""Test support of edge bearings for directed and undirected graphs."""
def test_bearings() -> None:
"""Test bearings and orientation entropy."""
G = ox.graph_from_place(place1, network_type="all")
G.add_node(0, x=location_point[1], y=location_point[0], street_count=0)
_ = ox.bearing.calculate_bearing(0, 0, 1, 1)
G = ox.add_edge_bearings(G)
G_proj = ox.project_graph(G)

# calculate entropy
Gu = ox.get_undirected(G)
entropy = ox.bearing.orientation_entropy(Gu, weight="length")
fig, ax = ox.plot.plot_orientation(Gu, area=True, title="Title")
fig, ax = ox.plot.plot_orientation(Gu, ax=ax, area=False, title="Title")

# test support of edge bearings for directed and undirected graphs
G = nx.MultiDiGraph(crs="epsg:4326")
G.add_node("point_1", x=0.0, y=0.0)
G.add_node("point_2", x=0.0, y=1.0) # latitude increases northward
G.add_edge("point_1", "point_2")
G = ox.distance.add_edge_lengths(G)
G = ox.add_edge_bearings(G)
with pytest.warns(UserWarning, match="Extracting directional bearings"):
bearings = ox.bearing._extract_edge_bearings(G, min_length=0.0, weight=None)
with pytest.warns(UserWarning, match="edge bearings will be directional"):
bearings = ox.bearing._extract_edge_bearings(G, min_length=0, weight=None)
assert list(bearings) == [0.0] # north
bearings = ox.bearing._extract_edge_bearings(G.to_undirected(), min_length=0.0, weight=None)
bearings = ox.bearing._extract_edge_bearings(
ox.utils_graph.get_undirected(G),
min_length=0,
weight=None,
)
assert list(bearings) == [0.0, 180.0] # north and south


Expand Down

0 comments on commit ea397d3

Please sign in to comment.