Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More API streamlining for v2 #1148

Merged
merged 13 commits into from
Mar 15, 2024
15 changes: 12 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,13 @@
Read the v2 [migration guide](https://github.com/gboeing/osmnx/issues/1123)

- add type annotations to all public and private functions throughout package (#1107)
- remove all functionality previously deprecated in v1 (#1113 #1122)
- remove all functionality previously deprecated in v1 (#1113 #1122 #1135 #1148)
- drop Python 3.8 support (#1106)
- bump minimum required numpy version to 1.21 for typing support (#1133)
- improve docstrings throughout package (#1116)
- improve logging and warnings throughout package (#1125)
- improve error messages throughout package (#1131)
- refactor save_graph_xml function and \_osm_xml module for a >5x speed improvement and bug fixes (#1135)
- remove settings module's osm_xml_node_attrs, osm_xml_node_tags, osm_xml_way_attrs, and osm_xml_way_tags settings (#1135)
- remove save_graph_xml function's node_tags, node_attrs, edge_tags, edge_attrs, merge_edges, oneway, api_version, and precision parameters (#1135)
- make save_graph_xml function accept only an unsimplified MultiDiGraph as its input data (#1135)
- replace save_graph_xml function's edge_tag_aggs tuple parameter with way_tag_aggs dict parameter (#1135)
- make consolidate_intersections function retain unique attribute values when consolidating nodes (#1144)
Expand All @@ -31,6 +29,7 @@ Read the v2 [migration guide](https://github.com/gboeing/osmnx/issues/1123)
- add node_attrs_include argument to simplification.simplify_graph function to flexibly relax strictness (#1145)
- rename simplification.simplify_graph endpoint_attrs argument to edge_attrs_differ (#1145)
- rename truncate.truncate_graph_dist max_dist argument to dist for consistency with rest of package (#1134)
- remove retain_all argument from all truncate module functions (#1148)
- rename settings module's default_accept_language, default_referer, and default_user_agent settings (#1129)
- rename settings module's memory, nominatim_endpoint, overpass_endpoint, and timeout settings (#1136)
- rename osm_xml module to \_osm_xml to make it private, as all its functions are private (#1113)
Expand All @@ -41,6 +40,16 @@ Read the v2 [migration guide](https://github.com/gboeing/osmnx/issues/1123)

- deprecate settings module's renamed or obsolete settings (#1138)
- deprecate save_graph_xml function's renamed or obsolete parameters (#1138)
- deprecate graph_from_xml tags and polygon function parameters (#1146)
- deprecate simplify_graph function's renamed endpoint_attrs argument (#1146)
- deprecate utils_graph.get_digraph function and replace it with covert.to_digraph function (#1146)
- deprecate utils_graph.get_undirected function and replace it with covert.to_undirected function (#1146)
- deprecate utils_graph.graph_to_gdfs function and replace it with covert.graph_to_gdfs function (#1146)
- deprecate utils_graph.graph_from_gdfs function and replace it with covert.graph_from_gdfs function (#1146)
- deprecate utils_graph.remove_isolated_nodes function and replace it with truncate.remove_isolated_nodes function (#1146)
- deprecate utils_graph.get_largest_component function and replace it with truncate.largest_component function (#1146)
- deprecate utils_graph.route_to_gdf function and replace it with routing.route_to_gdf function (#1146)
- deprecate speed module and move all of its functionality to the routing module (#1146)

## 1.9.1 (2024-02-01)

Expand Down
24 changes: 8 additions & 16 deletions docs/source/internals-reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ osmnx.bearing module
:private-members:
:noindex:

osmnx.convert module
---------------------

.. automodule:: osmnx.convert
:members:
:private-members:
:noindex:

osmnx.distance module
---------------------

Expand Down Expand Up @@ -139,14 +147,6 @@ osmnx.simplification module
:private-members:
:noindex:

osmnx.speed module
------------------

.. automodule:: osmnx.speed
:members:
:private-members:
:noindex:

osmnx.stats module
------------------

Expand Down Expand Up @@ -179,14 +179,6 @@ osmnx.utils_geo module
:private-members:
:noindex:

osmnx.utils_graph module
------------------------

.. automodule:: osmnx.utils_graph
:members:
:private-members:
:noindex:

osmnx._version module
---------------------

Expand Down
18 changes: 6 additions & 12 deletions docs/source/user-reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ osmnx.bearing module
.. automodule:: osmnx.bearing
:members:

osmnx.convert module
--------------------

.. automodule:: osmnx.convert
:members:

osmnx.distance module
---------------------

Expand Down Expand Up @@ -79,12 +85,6 @@ osmnx.simplification module
.. automodule:: osmnx.simplification
:members:

osmnx.speed module
------------------

.. automodule:: osmnx.speed
:members:

osmnx.stats module
------------------

Expand All @@ -108,9 +108,3 @@ osmnx.utils_geo module

.. automodule:: osmnx.utils_geo
:members:

osmnx.utils_graph module
------------------------

.. automodule:: osmnx.utils_graph
:members:
11 changes: 4 additions & 7 deletions osmnx/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
# by exposing these functions directly in the package's namespace.
from .bearing import add_edge_bearings as add_edge_bearings
from .bearing import orientation_entropy as orientation_entropy
from .convert import graph_from_gdfs as graph_from_gdfs
from .convert import graph_to_gdfs as graph_to_gdfs
from .distance import nearest_edges as nearest_edges
from .distance import nearest_nodes as nearest_nodes
from .elevation import add_edge_grades as add_edge_grades
Expand Down Expand Up @@ -38,19 +40,14 @@
from .plot import plot_graph_route as plot_graph_route
from .plot import plot_graph_routes as plot_graph_routes
from .plot import plot_orientation as plot_orientation
from .projection import project_gdf as project_gdf
from .projection import project_graph as project_graph
from .routing import add_edge_speeds as add_edge_speeds
from .routing import add_edge_travel_times as add_edge_travel_times
from .routing import k_shortest_paths as k_shortest_paths
from .routing import shortest_path as shortest_path
from .simplification import consolidate_intersections as consolidate_intersections
from .simplification import simplify_graph as simplify_graph
from .speed import add_edge_speeds as add_edge_speeds
from .speed import add_edge_travel_times as add_edge_travel_times
from .stats import basic_stats as basic_stats
from .utils import citation as citation
from .utils import log as log
from .utils import ts as ts
from .utils_graph import get_digraph as get_digraph
from .utils_graph import get_undirected as get_undirected
from .utils_graph import graph_from_gdfs as graph_from_gdfs
from .utils_graph import graph_to_gdfs as graph_to_gdfs
7 changes: 4 additions & 3 deletions osmnx/_osm_xml.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,11 @@
import networkx as nx
import pandas as pd

from . import convert
from . import projection
from . import settings
from . import truncate
from . import utils
from . import utils_graph
from ._errors import GraphSimplificationError
from ._version import __version__ as osmnx_version

Expand Down Expand Up @@ -209,7 +210,7 @@ def _save_graph_xml(
filepath.parent.mkdir(parents=True, exist_ok=True)

# convert graph to node/edge gdfs and create dict of spatial bounds
gdf_nodes, gdf_edges = utils_graph.graph_to_gdfs(G, fill_edge_geometry=False)
gdf_nodes, gdf_edges = convert.graph_to_gdfs(G, fill_edge_geometry=False)
coords = [str(round(c, PRECISION)) for c in gdf_nodes.unary_union.bounds]
bounds = dict(zip(["minlon", "minlat", "maxlon", "maxlat"], coords))

Expand Down Expand Up @@ -414,7 +415,7 @@ def _sort_nodes(G: nx.MultiDiGraph, osmid: int) -> list[int]:
# note this is destructive and will be missing in the saved data.
G_ = G.copy()
G_.remove_edges_from(nx.find_cycle(G_))
G_ = utils_graph.remove_isolated_nodes(G_)
G_ = truncate.remove_isolated_nodes(G_)
ordered_nodes = _sort_nodes(G_, osmid)
msg = f"Had to remove a cycle from way {str(osmid)!r} for topological sort"
utils.log(msg, level=lg.WARNING)
Expand Down
2 changes: 1 addition & 1 deletion osmnx/bearing.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ def _extract_edge_bearings(
msg = (
"`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`."
"per edge), pass a MultiGraph instead. Use `convert.to_undirected`."
)
warn(msg, category=UserWarning, stacklevel=2)
return bearings_array
Expand Down
103 changes: 5 additions & 98 deletions osmnx/utils_graph.py → osmnx/convert.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Graph utility functions."""
"""Convert spatial graphs to/from different data types."""

from __future__ import annotations

Expand Down Expand Up @@ -309,105 +309,12 @@ def graph_from_gdfs(
return G


def route_to_gdf(
G: nx.MultiDiGraph,
route: list[int],
*,
weight: str = "length",
) -> gpd.GeoDataFrame:
"""
Return a GeoDataFrame of the edges in a path, in order.

Parameters
----------
G
Input graph.
route
Node IDs constituting the path.
weight
Attribute value to minimize when choosing between parallel edges.

Returns
-------
gdf_edges
"""
pairs = zip(route[:-1], route[1:])
uvk = ((u, v, min(G[u][v].items(), key=lambda i: i[1][weight])[0]) for u, v in pairs)
return graph_to_gdfs(G.subgraph(route), nodes=False).loc[uvk]


def remove_isolated_nodes(G: nx.MultiDiGraph) -> nx.MultiDiGraph:
"""
Remove from a graph all nodes that have no incident edges.

Parameters
----------
G
Graph from which to remove isolated nodes.

Returns
-------
G
Graph with all isolated nodes removed.
"""
# make a copy to not mutate original graph object caller passed in
G = G.copy()

# get the set of all isolated nodes, then remove them
isolated_nodes = {node for node, degree in G.degree() if degree < 1}
G.remove_nodes_from(isolated_nodes)

msg = f"Removed {len(isolated_nodes):,} isolated nodes"
utils.log(msg, level=lg.INFO)
return G


def get_largest_component(G: nx.MultiDiGraph, *, strongly: bool = False) -> nx.MultiDiGraph:
"""
Return subgraph of `G`'s largest weakly or strongly connected component.

Parameters
----------
G
Input graph.
strongly
If True, return the largest strongly connected component. Otherwise
return the largest weakly connected component.

Returns
-------
G
The largest connected component subgraph of the original graph.
"""
if strongly:
kind = "strongly"
is_connected = nx.is_strongly_connected
connected_components = nx.strongly_connected_components
else:
kind = "weakly"
is_connected = nx.is_weakly_connected
connected_components = nx.weakly_connected_components

if not is_connected(G):
# get all the connected components in graph then identify the largest
largest_cc = max(connected_components(G), key=len)
n = len(G)

# induce (frozen) subgraph then unfreeze it by making new MultiDiGraph
G = nx.MultiDiGraph(G.subgraph(largest_cc))

msg = f"Got largest {kind} connected component ({len(G):,} of {n:,} total nodes)"
utils.log(msg, level=lg.INFO)

return G


def get_digraph(G: nx.MultiDiGraph, *, weight: str = "length") -> nx.DiGraph:
def to_digraph(G: nx.MultiDiGraph, *, weight: str = "length") -> nx.DiGraph:
"""
Convert MultiDiGraph to DiGraph.

Chooses between parallel edges by minimizing `weight` attribute value. See
also `get_undirected` to convert MultiDiGraph to MultiGraph.
also `to_undirected` to convert MultiDiGraph to MultiGraph.

Parameters
----------
Expand Down Expand Up @@ -440,12 +347,12 @@ def get_digraph(G: nx.MultiDiGraph, *, weight: str = "length") -> nx.DiGraph:
return nx.DiGraph(G)


def get_undirected(G: nx.MultiDiGraph) -> nx.MultiGraph:
def to_undirected(G: nx.MultiDiGraph) -> nx.MultiGraph:
"""
Convert MultiDiGraph to undirected MultiGraph.

Maintains parallel edges only if their geometries differ. See also
`get_digraph` to convert MultiDiGraph to DiGraph.
`to_digraph` to convert MultiDiGraph to DiGraph.

Parameters
----------
Expand Down
6 changes: 3 additions & 3 deletions osmnx/distance.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@
from shapely.geometry import Point
from shapely.strtree import STRtree

from . import convert
from . import projection
from . import utils
from . import utils_graph

# scipy is optional dependency for projected nearest-neighbor search
try:
Expand Down Expand Up @@ -350,7 +350,7 @@ def nearest_nodes(
msg = "`X` and `Y` cannot contain nulls."
raise ValueError(msg)

nodes = utils_graph.graph_to_gdfs(G, edges=False, node_geometry=False)[["x", "y"]]
nodes = convert.graph_to_gdfs(G, edges=False, node_geometry=False)[["x", "y"]]
nn_array: npt.NDArray[np.int64]
dist_array: npt.NDArray[np.float64]

Expand Down Expand Up @@ -502,7 +502,7 @@ def nearest_edges(
if np.isnan(X_arr).any() or np.isnan(Y_arr).any(): # pragma: no cover
msg = "`X` and `Y` cannot contain nulls."
raise ValueError(msg)
geoms = utils_graph.graph_to_gdfs(G, nodes=False)["geometry"]
geoms = convert.graph_to_gdfs(G, nodes=False)["geometry"]
ne_array: npt.NDArray[np.object_] # array of tuple[int, int, int]
dist_array: npt.NDArray[np.float64]

Expand Down
4 changes: 2 additions & 2 deletions osmnx/elevation.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@
import requests

from . import _http
from . import convert
from . import settings
from . import utils
from . import utils_graph
from ._errors import InsufficientResponseError

if TYPE_CHECKING:
Expand Down Expand Up @@ -155,7 +155,7 @@ def add_node_elevations_raster(
gdal.UseExceptions()
gdal.BuildVRT(filepath, filepaths).FlushCache()

nodes = utils_graph.graph_to_gdfs(G, edges=False, node_geometry=False)[["x", "y"]]
nodes = convert.graph_to_gdfs(G, edges=False, node_geometry=False)[["x", "y"]]
if cpus == 1:
elevs = dict(_query_raster(nodes, filepath, band))
else:
Expand Down
Loading