Skip to content

Commit

Permalink
Merge pull request #1196 from gboeing/coords
Browse files Browse the repository at this point in the history
Standardize bbox coord order to left, bottom, right, top
  • Loading branch information
gboeing authored Jul 21, 2024
2 parents c2828f6 + 3e6bfa6 commit 329562a
Show file tree
Hide file tree
Showing 9 changed files with 57 additions and 59 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Read the v2 [migration guide](https://github.com/gboeing/osmnx/issues/1123)
- make optional function parameters keyword-only throughout package (#1134)
- make dist function parameters required rather than optional throughout package (#1134)
- make utils_geo.bbox_from_point function return a tuple of floats for consistency with rest of package (#1113)
- make bounding box coordinate order consistently left, bottom, right, top (#1196)
- 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)
- remove settings module's deprecated and now replaced settings (#1129 #1136)
Expand Down
2 changes: 1 addition & 1 deletion osmnx/features.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ def features_from_bbox(
Parameters
----------
bbox
Bounding box as `(north, south, east, west)`. Coordinates should be in
Bounding box as `(left, bottom, right, top)`. Coordinates should be in
unprojected latitude-longitude degrees (EPSG:4326).
tags
Tags for finding elements in the selected area. Results are the union,
Expand Down
10 changes: 5 additions & 5 deletions osmnx/geocoder.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,15 +191,15 @@ def _geocode_query_to_gdf(
utils.log(msg, level=lg.WARNING)

# build the GeoJSON feature from the chosen result
south, north, west, east = result["boundingbox"]
bottom, top, left, right = result["boundingbox"]
feature = {
"type": "Feature",
"geometry": result["geojson"],
"properties": {
"bbox_north": north,
"bbox_south": south,
"bbox_east": east,
"bbox_west": west,
"bbox_west": left,
"bbox_south": bottom,
"bbox_east": right,
"bbox_north": top,
},
}

Expand Down
2 changes: 1 addition & 1 deletion osmnx/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def graph_from_bbox(
Parameters
----------
bbox
Bounding box as `(north, south, east, west)`. Coordinates should be in
Bounding box as `(left, bottom, right, top)`. Coordinates should be in
unprojected latitude-longitude degrees (EPSG:4326).
network_type
{"all", "all_public", "bike", "drive", "drive_service", "walk"}
Expand Down
37 changes: 17 additions & 20 deletions osmnx/plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ def plot_graph( # noqa: PLR0913
Opacity of the edges. If you passed RGBa values to `edge_color`, set
`edge_alpha=None` to use the alpha channel in `edge_color`.
bbox
Bounding box as `(north, south, east, west)`. If None, calculate it
Bounding box as `(left, bottom, right, top)`. If None, calculate it
from spatial extents of plotted geometries.
show
If True, call `pyplot.show()` to show the figure.
Expand Down Expand Up @@ -272,11 +272,11 @@ def plot_graph( # noqa: PLR0913
padding = 0.0
if bbox is None:
try:
west, south, east, north = gdf_edges.total_bounds
left, bottom, right, top = gdf_edges.total_bounds
except NameError:
west, south = gdf_nodes.min()
east, north = gdf_nodes.max()
bbox = north, south, east, west
left, bottom = gdf_nodes.min()
right, top = gdf_nodes.max()
bbox = left, bottom, right, top
padding = 0.02 # pad 2% to not cut off peripheral nodes' circles

# configure axes appearance, save/show figure as specified, and return
Expand Down Expand Up @@ -478,9 +478,8 @@ def plot_figure_ground(
G
An unprojected graph.
dist
How many meters to extend plot's bounding box north, south, east, and
west from the graph's center point. Default corresponds to a square
mile bounding box.
How many meters to extend plot's bounding box from the graph's center
point. Default corresponds to a square mile bounding box.
street_widths
Dict keys are street types (ie, OSM "highway" tags) and values are the
widths to plot them, in pixels.
Expand Down Expand Up @@ -612,7 +611,7 @@ def plot_footprints( # noqa: PLR0913
bgcolor
Background color of the figure.
bbox
Bounding box as `(north, south, east, west)`. If None, calculate it
Bounding box as `(left, bottom, right, top)`. If None, calculate it
from the spatial extents of the geometries in `gdf`.
show
If True, call `pyplot.show()` to show the figure.
Expand Down Expand Up @@ -645,12 +644,10 @@ def plot_footprints( # noqa: PLR0913

# determine figure extents
if bbox is None:
west, south, east, north = gdf.total_bounds
else:
north, south, east, west = bbox
bbox = tuple(gdf.total_bounds)

# configure axes appearance, save/show figure as specified, and return
ax = _config_ax(ax, gdf.crs, (north, south, east, west), 0) # type: ignore[arg-type]
ax = _config_ax(ax, gdf.crs, bbox, 0) # type: ignore[arg-type]
fig, ax = _save_and_show(
fig=fig,
ax=ax,
Expand Down Expand Up @@ -960,7 +957,7 @@ def _config_ax(ax: Axes, crs: Any, bbox: tuple[float, float, float, float], padd
crs
The coordinate reference system of the plotted geometries.
bbox
Bounding box as `(north, south, east, west)`.
Bounding box as `(left, bottom, right, top)`.
padding
Relative padding to add around `bbox`.
Expand All @@ -969,11 +966,11 @@ def _config_ax(ax: Axes, crs: Any, bbox: tuple[float, float, float, float], padd
ax
"""
# set the axes view limits to bbox + relative padding
north, south, east, west = bbox
padding_ns = (north - south) * padding
padding_ew = (east - west) * padding
ax.set_ylim((south - padding_ns, north + padding_ns))
ax.set_xlim((west - padding_ew, east + padding_ew))
left, bottom, right, top = bbox
padding_ns = (top - bottom) * padding
padding_ew = (right - left) * padding
ax.set_ylim((bottom - padding_ns, top + padding_ns))
ax.set_xlim((left - padding_ew, right + padding_ew))

# set margins to zero, point ticks inward, turn off ax border and x/y axis
# so there is no space around the plot
Expand All @@ -989,7 +986,7 @@ def _config_ax(ax: Axes, crs: Any, bbox: tuple[float, float, float, float], padd
ax.set_aspect("equal")
else:
# if not projected, conform aspect ratio to not stretch plot
cos_lat = np.cos((south + north) / 2 / 180 * np.pi)
cos_lat = np.cos((bottom + top) / 2 / 180 * np.pi)
ax.set_aspect(1 / cos_lat)

return ax
Expand Down
5 changes: 2 additions & 3 deletions osmnx/simplification.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@

import geopandas as gpd
import networkx as nx
import numpy as np
import pandas as pd
from shapely import LineString
from shapely import Point
Expand Down Expand Up @@ -622,7 +621,7 @@ def _consolidate_intersections_rebuild_graph( # noqa: C901,PLR0912,PLR0915
Keys are node attribute names and values are aggregation functions
(anything accepted as an argument by `pandas.agg`). Node attributes
not in `node_attr_aggs` will contain the unique values across the
merged nodes. If None, defaults to `{"elevation": numpy.mean}`.
merged nodes. If None, defaults to `{"elevation": "mean"}`.
Returns
-------
Expand All @@ -632,7 +631,7 @@ def _consolidate_intersections_rebuild_graph( # noqa: C901,PLR0912,PLR0915
"""
# default node attributes to aggregate upon consolidation
if node_attr_aggs is None:
node_attr_aggs = {"elevation": np.mean}
node_attr_aggs = {"elevation": "mean"}

# STEP 1
# buffer nodes to passed-in distance and merge overlaps. turn merged nodes
Expand Down
2 changes: 1 addition & 1 deletion osmnx/truncate.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ def truncate_graph_bbox(
G
Input graph.
bbox
Bounding box as `(north, south, east, west)`.
Bounding box as `(left, bottom, right, top)`.
truncate_by_edge
If True, retain nodes outside bounding box if at least one of node's
neighbors is within the bounding box.
Expand Down
41 changes: 21 additions & 20 deletions osmnx/utils_geo.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,11 +170,11 @@ def _quadrat_cut_geometry(geometry: Polygon | MultiPolygon, quadrat_width: float
min_num = 3

# create n evenly spaced points between the min and max x and y bounds
west, south, east, north = geometry.bounds
x_num = int(np.ceil((east - west) / quadrat_width) + 1)
y_num = int(np.ceil((north - south) / quadrat_width) + 1)
x_points = np.linspace(west, east, num=max(x_num, min_num))
y_points = np.linspace(south, north, num=max(y_num, min_num))
left, bottom, right, top = geometry.bounds
x_num = int(np.ceil((right - left) / quadrat_width) + 1)
y_num = int(np.ceil((top - bottom) / quadrat_width) + 1)
x_points = np.linspace(left, right, num=max(x_num, min_num))
y_points = np.linspace(bottom, top, num=max(y_num, min_num))

# create a quadrat grid of lines at each of the evenly spaced points
vertical_lines = [LineString([(x, y_points[0]), (x, y_points[-1])]) for x in x_points]
Expand Down Expand Up @@ -347,8 +347,8 @@ def bbox_from_point(
"""
Create a bounding box around a (lat, lon) point.
Create a bounding box some distance (in meters) in each direction (north,
south, east, and west) from the center point and optionally project it.
Create a bounding box some distance (in meters) in each direction (top,
bottom, right, and left) from the center point and optionally project it.
Parameters
----------
Expand All @@ -364,31 +364,32 @@ def bbox_from_point(
Returns
-------
bbox or bbox, crs
`(north, south, east, west)` or `((north, south, east, west), crs)`.
`(left, bottom, right, top)` or `((left, bottom, right, top), crs)`.
"""
EARTH_RADIUS_M = 6_371_009 # meters
lat, lon = point

delta_lat = (dist / EARTH_RADIUS_M) * (180 / np.pi)
delta_lon = (dist / EARTH_RADIUS_M) * (180 / np.pi) / np.cos(lat * np.pi / 180)
north = lat + delta_lat
south = lat - delta_lat
east = lon + delta_lon
west = lon - delta_lon
top = lat + delta_lat
bottom = lat - delta_lat
right = lon + delta_lon
left = lon - delta_lon
bbox = left, bottom, right, top

if project_utm:
bbox_poly = bbox_to_poly(bbox=(north, south, east, west))
bbox_poly = bbox_to_poly(bbox=bbox)
bbox_proj, crs_proj = projection.project_geometry(bbox_poly)
west, south, east, north = bbox_proj.bounds
bbox = bbox_proj.bounds

msg = f"Created bbox {dist} m from {point}: {north},{south},{east},{west}"
msg = f"Created bbox {dist} meters from {point}: {bbox}"
utils.log(msg, level=lg.INFO)

if project_utm and return_crs:
return (north, south, east, west), crs_proj
return bbox, crs_proj

# otherwise
return north, south, east, west
return bbox


def bbox_to_poly(bbox: tuple[float, float, float, float]) -> Polygon:
Expand All @@ -398,11 +399,11 @@ def bbox_to_poly(bbox: tuple[float, float, float, float]) -> Polygon:
Parameters
----------
bbox
Bounding box as `(north, south, east, west)`.
Bounding box as `(left, bottom, right, top)`.
Returns
-------
polygon
"""
north, south, east, west = bbox
return Polygon([(west, south), (east, south), (east, north), (west, north)])
left, bottom, right, top = bbox
return Polygon([(left, bottom), (right, bottom), (right, top), (left, top)])
16 changes: 8 additions & 8 deletions tests/test_osmnx.py
Original file line number Diff line number Diff line change
Expand Up @@ -535,17 +535,17 @@ def test_save_load() -> None: # noqa: PLR0915
# create list, set, and dict attributes for nodes and edges
rand_ints_nodes = np.random.default_rng().integers(low=0, high=10, size=len(G.nodes))
rand_ints_edges = np.random.default_rng().integers(low=0, high=10, size=len(G.edges))
list_node_attrs = {n: [n, r] for n, r in zip(G.nodes, rand_ints_nodes)}
list_node_attrs = {n: [n, int(r)] for n, r in zip(G.nodes, rand_ints_nodes)}
nx.set_node_attributes(G, list_node_attrs, "test_list")
list_edge_attrs = {e: [e, r] for e, r in zip(G.edges, rand_ints_edges)}
list_edge_attrs = {e: [e, int(r)] for e, r in zip(G.edges, rand_ints_edges)}
nx.set_edge_attributes(G, list_edge_attrs, "test_list")
set_node_attrs = {n: {n, r} for n, r in zip(G.nodes, rand_ints_nodes)}
set_node_attrs = {n: {n, int(r)} for n, r in zip(G.nodes, rand_ints_nodes)}
nx.set_node_attributes(G, set_node_attrs, "test_set")
set_edge_attrs = {e: {e, r} for e, r in zip(G.edges, rand_ints_edges)}
set_edge_attrs = {e: {e, int(r)} for e, r in zip(G.edges, rand_ints_edges)}
nx.set_edge_attributes(G, set_edge_attrs, "test_set")
dict_node_attrs = {n: {n: r} for n, r in zip(G.nodes, rand_ints_nodes)}
dict_node_attrs = {n: {n: int(r)} for n, r in zip(G.nodes, rand_ints_nodes)}
nx.set_node_attributes(G, dict_node_attrs, "test_dict")
dict_edge_attrs = {e: {e: r} for e, r in zip(G.edges, rand_ints_edges)}
dict_edge_attrs = {e: {e: int(r)} for e, r in zip(G.edges, rand_ints_edges)}
nx.set_edge_attributes(G, dict_edge_attrs, "test_dict")

# save/load graph as graphml file
Expand Down Expand Up @@ -664,12 +664,12 @@ def test_features() -> None:

# features_from_bbox - bounding box query to return no data
with pytest.raises(ox._errors.InsufficientResponseError):
gdf = ox.features_from_bbox(bbox=(-2.000, -2.001, -2.000, -2.001), tags={"building": True})
gdf = ox.features_from_bbox(bbox=(-2.001, -2.001, -2.000, -2.000), tags={"building": True})

# features_from_bbox - successful
gdf = ox.features_from_bbox(bbox, tags=tags1)
fig, ax = ox.plot_footprints(gdf)
fig, ax = ox.plot_footprints(gdf, ax=ax, bbox=(10, 0, 10, 0))
fig, ax = ox.plot_footprints(gdf, ax=ax, bbox=(0, 0, 10, 10))

# features_from_point - tests multipolygon creation
gdf = ox.utils_geo.bbox_from_point(location_point, dist=500)
Expand Down

0 comments on commit 329562a

Please sign in to comment.