From eec57dadc7c4aaae9e9f08df8f54ad1bdd3e3fba Mon Sep 17 00:00:00 2001 From: Andrey Khitryy Date: Tue, 6 Aug 2024 17:56:21 +0300 Subject: [PATCH 1/7] Add support for Overpass Union --- osmnx/_overpass.py | 15 +++++++++++---- osmnx/graph.py | 10 +++++----- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/osmnx/_overpass.py b/osmnx/_overpass.py index 6c7c2f53..5883494d 100644 --- a/osmnx/_overpass.py +++ b/osmnx/_overpass.py @@ -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. @@ -371,7 +371,13 @@ def _download_overpass_network( """ # create a filter 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_filter = [] + if isinstance(custom_filter, list): + way_filter = custom_filter + elif isinstance(custom_filter, str): + way_filter = [custom_filter] + else: + way_filter = [_get_network_filter(network_type)] # create overpass settings string overpass_settings = _make_overpass_settings() @@ -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_str in way_filter: + query_str = f"{overpass_settings};(way{way_filter_str}(poly:{polygon_coord_str!r});>;);out;" + yield _overpass_request(OrderedDict(data=query_str)) def _download_overpass_features( diff --git a/osmnx/graph.py b/osmnx/graph.py index eea3baec..f311f751 100644 --- a/osmnx/graph.py +++ b/osmnx/graph.py @@ -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. @@ -118,7 +118,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. @@ -210,7 +210,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. @@ -293,7 +293,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). @@ -382,7 +382,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. From 4d92ce7268b1c641ee0b81d041f67a75ee187021 Mon Sep 17 00:00:00 2001 From: Andrey Khitryy Date: Tue, 13 Aug 2024 09:41:45 +0300 Subject: [PATCH 2/7] Update function docs --- osmnx/graph.py | 55 ++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 40 insertions(+), 15 deletions(-) diff --git a/osmnx/graph.py b/osmnx/graph.py index f311f751..6ee5cd94 100644 --- a/osmnx/graph.py +++ b/osmnx/graph.py @@ -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 single `str` is provded, intersection will be used to combine results, + e.g `'[maxspeed=50][lanes=2]'` will query all ways with maximum speed + of 50 and having two lanes. + If `list[str]`, union will be used, e.g. `['[maxspeed=50]', '[lanes=2]']` + will query all ways with maximum speed of 50 or having 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 ------- @@ -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 single `str` is provded, intersection will be used to combine results, + e.g `'[maxspeed=50][lanes=2]'` will query all ways with maximum speed + of 50 and having two lanes. + If `list[str]`, union will be used, e.g. `['[maxspeed=50]', '[lanes=2]']` + will query all ways with maximum speed of 50 or having 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 ------- @@ -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 single `str` is provded, intersection will be used to combine results, + e.g `'[maxspeed=50][lanes=2]'` will query all ways with maximum speed + of 50 and having two lanes. + If `list[str]`, union will be used, e.g. `['[maxspeed=50]', '[lanes=2]']` + will query all ways with maximum speed of 50 or having 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 ------- @@ -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 single `str` is provded, intersection will be used to combine results, + e.g `'[maxspeed=50][lanes=2]'` will query all ways with maximum speed + of 50 and having two lanes. + If `list[str]`, union will be used, e.g. `['[maxspeed=50]', '[lanes=2]']` + will query all ways with maximum speed of 50 or having 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 ------- @@ -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 single `str` is provded, intersection will be used to combine results, + e.g `'[maxspeed=50][lanes=2]'` will query all ways with maximum speed + of 50 and having two lanes. + If `list[str]`, union will be used, e.g. `['[maxspeed=50]', '[lanes=2]']` + will query all ways with maximum speed of 50 or having 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 ------- From 2b10aacb30c98a0817d534a6b5c3e17eae2fc2fe Mon Sep 17 00:00:00 2001 From: Nick Date: Wed, 14 Aug 2024 11:10:24 +0800 Subject: [PATCH 3/7] Optimize _remove_polygon_holes and _create_gdf for performance --- CHANGELOG.md | 2 ++ osmnx/features.py | 19 +++++++++++++------ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7cfd2dfe..8a505dc3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,8 @@ Read the v2 [migration guide](https://github.com/gboeing/osmnx/issues/1123) - rename osm_xml module to \_osm_xml to make it private, as all its functions are private (#1113) - rename private \_downloader module to \_http (#1114) - remove unnecessary private \_api module (#1114) +- optimize \_remove_polygon_holes function for improved performance with complex geometries (#1200) +- enhance efficiency of \_create_gdf function in feature processing (#1200) ## 1.9.4 (2024-07-24) diff --git a/osmnx/features.py b/osmnx/features.py index 7b872bb2..d13f2cdc 100644 --- a/osmnx/features.py +++ b/osmnx/features.py @@ -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 @@ -409,8 +410,15 @@ def _create_gdf( # 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(idx) + .sort_index() + ) return _filter_features(gdf, polygon, tags) @@ -636,10 +644,9 @@ def _remove_polygon_holes( # otherwise, remove from each outer poly each inner poly 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 From 7dca7f59145fe896cd695a2f4c00765b87e7b9ae Mon Sep 17 00:00:00 2001 From: Andrey Khitryy Date: Wed, 14 Aug 2024 13:13:23 +0300 Subject: [PATCH 4/7] Reformat according to code style --- osmnx/_overpass.py | 4 +++- osmnx/graph.py | 30 +++++++++++++++--------------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/osmnx/_overpass.py b/osmnx/_overpass.py index 5883494d..a2c1082c 100644 --- a/osmnx/_overpass.py +++ b/osmnx/_overpass.py @@ -391,7 +391,9 @@ def _download_overpass_network( # the '>' makes it recurse so we get ways and the ways' nodes. for polygon_coord_str in polygon_coord_strs: for way_filter_str in way_filter: - query_str = f"{overpass_settings};(way{way_filter_str}(poly:{polygon_coord_str!r});>;);out;" + query_str = ( + f"{overpass_settings};(way{way_filter_str}(poly:{polygon_coord_str!r});>;);out;" + ) yield _overpass_request(OrderedDict(data=query_str)) diff --git a/osmnx/graph.py b/osmnx/graph.py index 6ee5cd94..1ef6d566 100644 --- a/osmnx/graph.py +++ b/osmnx/graph.py @@ -77,14 +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"]'`. - If single `str` is provded, intersection will be used to combine results, + e.g. `'["power"~"line"]' or '["highway"~"motorway|trunk"]'`. + If single `str` is provded, intersection will be used to combine results, e.g `'[maxspeed=50][lanes=2]'` will query all ways with maximum speed of 50 and having two lanes. If `list[str]`, union will be used, e.g. `['[maxspeed=50]', '[lanes=2]']` will query all ways with maximum speed of 50 or having two lanes. Also pass in a `network_type` that is in `settings.bidirectional_network_types` - if you want the graph to be fully bidirectional. + if you want the graph to be fully bidirectional. Returns ------- @@ -165,14 +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"]'`. - If single `str` is provded, intersection will be used to combine results, + e.g. `'["power"~"line"]' or '["highway"~"motorway|trunk"]'`. + If single `str` is provded, intersection will be used to combine results, e.g `'[maxspeed=50][lanes=2]'` will query all ways with maximum speed of 50 and having two lanes. If `list[str]`, union will be used, e.g. `['[maxspeed=50]', '[lanes=2]']` will query all ways with maximum speed of 50 or having two lanes. Also pass in a `network_type` that is in `settings.bidirectional_network_types` - if you want the graph to be fully bidirectional. + if you want the graph to be fully bidirectional. Returns ------- @@ -261,14 +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"]'`. - If single `str` is provded, intersection will be used to combine results, + e.g. `'["power"~"line"]' or '["highway"~"motorway|trunk"]'`. + If single `str` is provded, intersection will be used to combine results, e.g `'[maxspeed=50][lanes=2]'` will query all ways with maximum speed of 50 and having two lanes. If `list[str]`, union will be used, e.g. `['[maxspeed=50]', '[lanes=2]']` will query all ways with maximum speed of 50 or having two lanes. Also pass in a `network_type` that is in `settings.bidirectional_network_types` - if you want the graph to be fully bidirectional. + if you want the graph to be fully bidirectional. Returns ------- @@ -356,14 +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"]'`. - If single `str` is provded, intersection will be used to combine results, + e.g. `'["power"~"line"]' or '["highway"~"motorway|trunk"]'`. + If single `str` is provded, intersection will be used to combine results, e.g `'[maxspeed=50][lanes=2]'` will query all ways with maximum speed of 50 and having two lanes. If `list[str]`, union will be used, e.g. `['[maxspeed=50]', '[lanes=2]']` will query all ways with maximum speed of 50 or having two lanes. Also pass in a `network_type` that is in `settings.bidirectional_network_types` - if you want the graph to be fully bidirectional. + if you want the graph to be fully bidirectional. Returns ------- @@ -435,14 +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"]'`. - If single `str` is provded, intersection will be used to combine results, + e.g. `'["power"~"line"]' or '["highway"~"motorway|trunk"]'`. + If single `str` is provded, intersection will be used to combine results, e.g `'[maxspeed=50][lanes=2]'` will query all ways with maximum speed of 50 and having two lanes. If `list[str]`, union will be used, e.g. `['[maxspeed=50]', '[lanes=2]']` will query all ways with maximum speed of 50 or having two lanes. Also pass in a `network_type` that is in `settings.bidirectional_network_types` - if you want the graph to be fully bidirectional. + if you want the graph to be fully bidirectional. Returns ------- From 87db2d41463a9a5333d56b1c816a5627634c8224 Mon Sep 17 00:00:00 2001 From: Geoff Boeing Date: Wed, 14 Aug 2024 13:44:53 -0700 Subject: [PATCH 5/7] fix mypy --- osmnx/plot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osmnx/plot.py b/osmnx/plot.py index 8f1b19e1..9615df7b 100644 --- a/osmnx/plot.py +++ b/osmnx/plot.py @@ -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: From 30a55c244ac85d31689bb61503cb222f6c3be022 Mon Sep 17 00:00:00 2001 From: Geoff Boeing Date: Wed, 14 Aug 2024 14:02:58 -0700 Subject: [PATCH 6/7] code cleanup --- CHANGELOG.md | 5 ++- osmnx/_overpass.py | 16 ++++------ osmnx/features.py | 5 ++- osmnx/graph.py | 80 +++++++++++++++++++++++----------------------- 4 files changed, 51 insertions(+), 55 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a505dc3..59138d91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) @@ -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) @@ -40,8 +41,6 @@ Read the v2 [migration guide](https://github.com/gboeing/osmnx/issues/1123) - rename osm_xml module to \_osm_xml to make it private, as all its functions are private (#1113) - rename private \_downloader module to \_http (#1114) - remove unnecessary private \_api module (#1114) -- optimize \_remove_polygon_holes function for improved performance with complex geometries (#1200) -- enhance efficiency of \_create_gdf function in feature processing (#1200) ## 1.9.4 (2024-07-24) diff --git a/osmnx/_overpass.py b/osmnx/_overpass.py index a2c1082c..53698926 100644 --- a/osmnx/_overpass.py +++ b/osmnx/_overpass.py @@ -369,15 +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 = [] + way_filters = [] if isinstance(custom_filter, list): - way_filter = custom_filter + way_filters = custom_filter elif isinstance(custom_filter, str): - way_filter = [custom_filter] + way_filters = [custom_filter] else: - way_filter = [_get_network_filter(network_type)] + way_filters = [_get_network_filter(network_type)] # create overpass settings string overpass_settings = _make_overpass_settings() @@ -390,10 +390,8 @@ 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: - for way_filter_str in way_filter: - query_str = ( - f"{overpass_settings};(way{way_filter_str}(poly:{polygon_coord_str!r});>;);out;" - ) + 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)) diff --git a/osmnx/features.py b/osmnx/features.py index d13f2cdc..42143139 100644 --- a/osmnx/features.py +++ b/osmnx/features.py @@ -409,14 +409,13 @@ def _create_gdf( raise CacheOnlyInterruptError(msg) # convert the elements into a GeoDataFrame of features - idx = ["element", "id"] gdf = ( gpd.GeoDataFrame( data=_process_features(elements, set(tags.keys())), geometry="geometry", crs=settings.default_crs, ) - .set_index(idx) + .set_index(["element", "id"]) .sort_index() ) return _filter_features(gdf, polygon, tags) @@ -641,7 +640,7 @@ 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: prepare(outer) diff --git a/osmnx/graph.py b/osmnx/graph.py index 1ef6d566..d450c0ed 100644 --- a/osmnx/graph.py +++ b/osmnx/graph.py @@ -77,14 +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"]'`. - If single `str` is provded, intersection will be used to combine results, - e.g `'[maxspeed=50][lanes=2]'` will query all ways with maximum speed - of 50 and having two lanes. - If `list[str]`, union will be used, e.g. `['[maxspeed=50]', '[lanes=2]']` - will query all ways with maximum speed of 50 or having two lanes. - 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 ------- @@ -165,14 +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"]'`. - If single `str` is provded, intersection will be used to combine results, - e.g `'[maxspeed=50][lanes=2]'` will query all ways with maximum speed - of 50 and having two lanes. - If `list[str]`, union will be used, e.g. `['[maxspeed=50]', '[lanes=2]']` - will query all ways with maximum speed of 50 or having two lanes. - 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 ------- @@ -261,14 +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"]'`. - If single `str` is provded, intersection will be used to combine results, - e.g `'[maxspeed=50][lanes=2]'` will query all ways with maximum speed - of 50 and having two lanes. - If `list[str]`, union will be used, e.g. `['[maxspeed=50]', '[lanes=2]']` - will query all ways with maximum speed of 50 or having two lanes. - 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 ------- @@ -356,14 +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"]'`. - If single `str` is provded, intersection will be used to combine results, - e.g `'[maxspeed=50][lanes=2]'` will query all ways with maximum speed - of 50 and having two lanes. - If `list[str]`, union will be used, e.g. `['[maxspeed=50]', '[lanes=2]']` - will query all ways with maximum speed of 50 or having two lanes. - 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 ------- @@ -435,14 +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"]'`. - If single `str` is provded, intersection will be used to combine results, - e.g `'[maxspeed=50][lanes=2]'` will query all ways with maximum speed - of 50 and having two lanes. - If `list[str]`, union will be used, e.g. `['[maxspeed=50]', '[lanes=2]']` - will query all ways with maximum speed of 50 or having two lanes. - 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 ------- From 0b3fe936902a42839e0c540161933194f2ec8e13 Mon Sep 17 00:00:00 2001 From: Geoff Boeing Date: Wed, 14 Aug 2024 15:32:53 -0700 Subject: [PATCH 7/7] add tests for union of multiple custom filters and for complex feature geometry creation --- tests/test_osmnx.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/test_osmnx.py b/tests/test_osmnx.py index d5d48f7f..c99a8e6c 100644 --- a/tests/test_osmnx.py +++ b/tests/test_osmnx.py @@ -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, @@ -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)