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

Improve test coverage #1206

Merged
merged 9 commits into from
Aug 14, 2024
Merged
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Read the v2 [migration guide](https://github.com/gboeing/osmnx/issues/1123)
- improve docstrings throughout package (#1116)
- improve logging and warnings throughout package (#1125)
- improve error messages throughout package (#1131)
- refactor features module for speed improvement and memory efficiency (#1157)
- refactor features module for speed improvement and memory efficiency (#1157 #1205)
- refactor save_graph_xml function and \_osm_xml module for speed improvement and bug fixes (#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)
Expand All @@ -26,6 +26,7 @@ Read the v2 [migration guide](https://github.com/gboeing/osmnx/issues/1123)
- handle implicit maxspeed values in add_edge_speeds function (#1153)
- change add_node_elevations_google default batch_size to 512 to match Google's limit (#1115)
- allow analysis of MultiDiGraph directional edge bearings and orientation (#1139)
- allow graph union queries through the custom_filter argument (#1204)
- fix graph projection creating useless lat and lon node attributes (#1144)
- fix bug in \_downloader.\_save_to_cache function usage (#1107)
- fix bug in handling requests ConnectionError when querying Overpass status endpoint (#1113)
Expand Down
17 changes: 12 additions & 5 deletions osmnx/_overpass.py
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,7 @@ def _create_overpass_features_query( # noqa: PLR0912
def _download_overpass_network(
polygon: Polygon | MultiPolygon,
network_type: str,
custom_filter: str | None,
custom_filter: str | list[str] | None,
) -> Iterator[dict[str, Any]]:
"""
Retrieve networked ways and nodes within boundary from the Overpass API.
Expand All @@ -369,9 +369,15 @@ def _download_overpass_network(
response_json
JSON response from the Overpass server.
"""
# create a filter to exclude certain kinds of ways based on the requested
# create filter(s) to exclude certain kinds of ways based on the requested
# network_type, if provided, otherwise use custom_filter
way_filter = custom_filter if custom_filter is not None else _get_network_filter(network_type)
way_filters = []
if isinstance(custom_filter, list):
way_filters = custom_filter
elif isinstance(custom_filter, str):
way_filters = [custom_filter]
else:
way_filters = [_get_network_filter(network_type)]

# create overpass settings string
overpass_settings = _make_overpass_settings()
Expand All @@ -384,8 +390,9 @@ def _download_overpass_network(
# pass exterior coordinates of each polygon in list to API, one at a time
# the '>' makes it recurse so we get ways and the ways' nodes.
for polygon_coord_str in polygon_coord_strs:
query_str = f"{overpass_settings};(way{way_filter}(poly:{polygon_coord_str!r});>;);out;"
yield _overpass_request(OrderedDict(data=query_str))
for way_filter in way_filters:
query_str = f"{overpass_settings};(way{way_filter}(poly:{polygon_coord_str!r});>;);out;"
yield _overpass_request(OrderedDict(data=query_str))


def _download_overpass_features(
Expand Down
22 changes: 14 additions & 8 deletions osmnx/features.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
from shapely import MultiPolygon
from shapely import Point
from shapely import Polygon
from shapely import prepare
from shapely.errors import GEOSException
from shapely.ops import linemerge
from shapely.ops import polygonize
Expand Down Expand Up @@ -408,9 +409,15 @@ def _create_gdf(
raise CacheOnlyInterruptError(msg)

# convert the elements into a GeoDataFrame of features
idx = ["element", "id"]
features = _process_features(elements, set(tags.keys()))
gdf = gpd.GeoDataFrame(features, geometry="geometry", crs=settings.default_crs).set_index(idx)
gdf = (
gpd.GeoDataFrame(
data=_process_features(elements, set(tags.keys())),
geometry="geometry",
crs=settings.default_crs,
)
.set_index(["element", "id"])
.sort_index()
)
return _filter_features(gdf, polygon, tags)


Expand Down Expand Up @@ -633,13 +640,12 @@ def _remove_polygon_holes(
# if there are no holes to remove, geom is the union of outer polygons
geometry = unary_union(outer_polygons)
else:
# otherwise, remove from each outer poly each inner poly it contains
# otherwise, remove from each outer poly all inner polys it contains
polygons_with_holes = []
for outer in outer_polygons:
for inner in inner_polygons:
if outer.contains(inner):
outer = outer.difference(inner) # noqa: PLW2901
polygons_with_holes.append(outer)
prepare(outer)
holes = [inner for inner in inner_polygons if outer.contains(inner)]
polygons_with_holes.append(outer.difference(unary_union(holes)))
geometry = unary_union(polygons_with_holes)

# ensure returned geometry is a Polygon or MultiPolygon
Expand Down
65 changes: 45 additions & 20 deletions osmnx/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def graph_from_bbox(
simplify: bool = True,
retain_all: bool = False,
truncate_by_edge: bool = False,
custom_filter: str | None = None,
custom_filter: str | list[str] | None = None,
) -> nx.MultiDiGraph:
"""
Download and create a graph within a lat-lon bounding box.
Expand Down Expand Up @@ -77,9 +77,14 @@ def graph_from_bbox(
neighbors is within the bounding box.
custom_filter
A custom ways filter to be used instead of the `network_type` presets,
e.g. `'["power"~"line"]' or '["highway"~"motorway|trunk"]'`. Also pass
in a `network_type` that is in `settings.bidirectional_network_types`
if you want the graph to be fully bidirectional.
e.g. `'["power"~"line"]' or '["highway"~"motorway|trunk"]'`. If `str`,
the intersection of keys/values will be used, e.g., `'[maxspeed=50][lanes=2]'`
will return all ways having both maxspeed of 50 and two lanes. If
`list`, the union of the `list` items will be used, e.g.,
`['[maxspeed=50]', '[lanes=2]']` will return all ways having either
maximum speed of 50 or two lanes. Also pass in a `network_type` that
is in `settings.bidirectional_network_types` if you want the graph to
be fully bidirectional.

Returns
-------
Expand Down Expand Up @@ -118,7 +123,7 @@ def graph_from_point(
simplify: bool = True,
retain_all: bool = False,
truncate_by_edge: bool = False,
custom_filter: str | None = None,
custom_filter: str | list[str] | None = None,
) -> nx.MultiDiGraph:
"""
Download and create a graph within some distance of a lat-lon point.
Expand Down Expand Up @@ -160,9 +165,14 @@ def graph_from_point(
neighbors is within the bounding box.
custom_filter
A custom ways filter to be used instead of the `network_type` presets,
e.g. `'["power"~"line"]' or '["highway"~"motorway|trunk"]'`. Also pass
in a `network_type` that is in `settings.bidirectional_network_types`
if you want the graph to be fully bidirectional.
e.g. `'["power"~"line"]' or '["highway"~"motorway|trunk"]'`. If `str`,
the intersection of keys/values will be used, e.g., `'[maxspeed=50][lanes=2]'`
will return all ways having both maxspeed of 50 and two lanes. If
`list`, the union of the `list` items will be used, e.g.,
`['[maxspeed=50]', '[lanes=2]']` will return all ways having either
maximum speed of 50 or two lanes. Also pass in a `network_type` that
is in `settings.bidirectional_network_types` if you want the graph to
be fully bidirectional.

Returns
-------
Expand Down Expand Up @@ -210,7 +220,7 @@ def graph_from_address(
simplify: bool = True,
retain_all: bool = False,
truncate_by_edge: bool = False,
custom_filter: str | None = None,
custom_filter: str | list[str] | None = None,
) -> nx.MultiDiGraph | tuple[nx.MultiDiGraph, tuple[float, float]]:
"""
Download and create a graph within some distance of an address.
Expand Down Expand Up @@ -251,9 +261,14 @@ def graph_from_address(
neighbors is within the bounding box.
custom_filter
A custom ways filter to be used instead of the `network_type` presets,
e.g. `'["power"~"line"]' or '["highway"~"motorway|trunk"]'`. Also pass
in a `network_type` that is in `settings.bidirectional_network_types`
if you want the graph to be fully bidirectional.
e.g. `'["power"~"line"]' or '["highway"~"motorway|trunk"]'`. If `str`,
the intersection of keys/values will be used, e.g., `'[maxspeed=50][lanes=2]'`
will return all ways having both maxspeed of 50 and two lanes. If
`list`, the union of the `list` items will be used, e.g.,
`['[maxspeed=50]', '[lanes=2]']` will return all ways having either
maximum speed of 50 or two lanes. Also pass in a `network_type` that
is in `settings.bidirectional_network_types` if you want the graph to
be fully bidirectional.

Returns
-------
Expand Down Expand Up @@ -293,7 +308,7 @@ def graph_from_place(
retain_all: bool = False,
truncate_by_edge: bool = False,
which_result: int | None | list[int | None] = None,
custom_filter: str | None = None,
custom_filter: str | list[str] | None = None,
) -> nx.MultiDiGraph:
"""
Download and create a graph within the boundaries of some place(s).
Expand Down Expand Up @@ -341,9 +356,14 @@ def graph_from_place(
(Multi)Polygon or raise an error if OSM doesn't return one.
custom_filter
A custom ways filter to be used instead of the `network_type` presets,
e.g. `'["power"~"line"]' or '["highway"~"motorway|trunk"]'`. Also pass
in a `network_type` that is in `settings.bidirectional_network_types`
if you want the graph to be fully bidirectional.
e.g. `'["power"~"line"]' or '["highway"~"motorway|trunk"]'`. If `str`,
the intersection of keys/values will be used, e.g., `'[maxspeed=50][lanes=2]'`
will return all ways having both maxspeed of 50 and two lanes. If
`list`, the union of the `list` items will be used, e.g.,
`['[maxspeed=50]', '[lanes=2]']` will return all ways having either
maximum speed of 50 or two lanes. Also pass in a `network_type` that
is in `settings.bidirectional_network_types` if you want the graph to
be fully bidirectional.

Returns
-------
Expand Down Expand Up @@ -382,7 +402,7 @@ def graph_from_polygon(
simplify: bool = True,
retain_all: bool = False,
truncate_by_edge: bool = False,
custom_filter: str | None = None,
custom_filter: str | list[str] | None = None,
) -> nx.MultiDiGraph:
"""
Download and create a graph within the boundaries of a (Multi)Polygon.
Expand Down Expand Up @@ -415,9 +435,14 @@ def graph_from_polygon(
neighbors is within the bounding box.
custom_filter
A custom ways filter to be used instead of the `network_type` presets,
e.g. `'["power"~"line"]' or '["highway"~"motorway|trunk"]'`. Also pass
in a `network_type` that is in `settings.bidirectional_network_types`
if you want the graph to be fully bidirectional.
e.g. `'["power"~"line"]' or '["highway"~"motorway|trunk"]'`. If `str`,
the intersection of keys/values will be used, e.g., `'[maxspeed=50][lanes=2]'`
will return all ways having both maxspeed of 50 and two lanes. If
`list`, the union of the `list` items will be used, e.g.,
`['[maxspeed=50]', '[lanes=2]']` will return all ways having either
maximum speed of 50 or two lanes. Also pass in a `network_type` that
is in `settings.bidirectional_network_types` if you want the graph to
be fully bidirectional.

Returns
-------
Expand Down
2 changes: 1 addition & 1 deletion osmnx/plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -853,7 +853,7 @@ def _get_colors_by_value(
# linearly map a color to each attribute value
normalizer = colors.Normalize(full_min, full_max)
scalar_mapper = cm.ScalarMappable(normalizer, colormaps[cmap])
color_series = vals.map(scalar_mapper.to_rgba).map(colors.to_hex)
color_series = vals.map(scalar_mapper.to_rgba).map(colors.to_hex) # type: ignore[arg-type]
color_series.loc[pd.isna(vals)] = na_color

else:
Expand Down
21 changes: 21 additions & 0 deletions tests/test_osmnx.py
Original file line number Diff line number Diff line change
Expand Up @@ -637,6 +637,10 @@ def test_graph_from() -> None:
network_type="all_public",
)

# test union of multiple custom filters
cf_union = ['["highway"~"tertiary"]', '["railway"~"tram"]']
G = ox.graph_from_point(location_point, dist=500, custom_filter=cf_union, retain_all=True)

ox.settings.overpass_memory = 1073741824
G = ox.graph_from_point(
location_point,
Expand Down Expand Up @@ -703,3 +707,20 @@ def test_features() -> None:
gdf = ox.features_from_xml(filename)
assert "Willow Street" in gdf["name"].to_numpy()
Path.unlink(Path(temp_filename))

# test the "island within a hole" and "touching inner rings" use cases
# https://wiki.openstreetmap.org/wiki/Relation:multipolygon#Island_within_a_hole
# https://wiki.openstreetmap.org/wiki/Relation:multipolygon#Touching_inner_rings
outer1 = Polygon(((0, 0), (4, 0), (4, 4), (0, 4)))
inner1 = Polygon(((1, 1), (2, 1), (2, 3), (1, 3)))
inner2 = Polygon(((2, 1), (3, 1), (3, 3), (2, 3)))
outer2 = Polygon(((1.5, 1.5), (2.5, 1.5), (2.5, 2.5), (1.5, 2.5)))
outer_polygons = [outer1, outer2]
inner_polygons = [inner1, inner2]
result = ox.features._remove_polygon_holes(outer_polygons, inner_polygons)
geom_wkt = (
"MULTIPOLYGON (((4 4, 4 0, 0 0, 0 4, 4 4), "
"(3 1, 3 3, 2 3, 1 3, 1 1, 2 1, 3 1)), "
"((2.5 2.5, 2.5 1.5, 1.5 1.5, 1.5 2.5, 2.5 2.5)))"
)
assert result == wkt.loads(geom_wkt)
Loading