From 7950a9b19a60cc1e105370bb9d1cfd8aa5771781 Mon Sep 17 00:00:00 2001 From: Geoff Boeing Date: Tue, 12 Mar 2024 09:51:59 -0700 Subject: [PATCH 1/7] add junction and railway to useful node tags --- osmnx/settings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osmnx/settings.py b/osmnx/settings.py index 4695e01b2..248ed6d07 100644 --- a/osmnx/settings.py +++ b/osmnx/settings.py @@ -114,7 +114,7 @@ API repeatedly for the same request. Default is `True`. useful_tags_node : list[str] OSM "node" tags to add as graph node attributes, when present in the data - retrieved from OSM. Default is `["highway", "ref"]`. + retrieved from OSM. Default is `["highway", "junction", "railway", "ref"]`. useful_tags_way : list[str] OSM "way" tags to add as graph edge attributes, when present in the data retrieved from OSM. Default is `["access", "area", "bridge", "est_width", @@ -162,7 +162,7 @@ requests_kwargs: dict[str, Any] = {} requests_timeout: float = 180 use_cache: bool = True -useful_tags_node: list[str] = ["highway", "ref"] +useful_tags_node: list[str] = ["highway", "junction", "railway", "ref"] useful_tags_way: list[str] = [ "access", "area", From 9aba6263e5cd4ed8ce27789431e02dbb3c159e53 Mon Sep 17 00:00:00 2001 From: Geoff Boeing Date: Tue, 12 Mar 2024 12:17:59 -0700 Subject: [PATCH 2/7] make node consolidation retain single or list of unique node attr values --- osmnx/_osm_xml.py | 7 ++++++- osmnx/simplification.py | 33 +++++++++++++++++++++++---------- 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/osmnx/_osm_xml.py b/osmnx/_osm_xml.py index 89c549b77..c18f95ff6 100644 --- a/osmnx/_osm_xml.py +++ b/osmnx/_osm_xml.py @@ -279,7 +279,12 @@ def _add_nodes_xml( node_element = SubElement(parent, "node", attrib=attrs) # add each node tag dict as its own SubElement of the node SubElement - tags = ({"k": k, "v": str(node[k])} for k in node_tags & node.keys() if pd.notna(node[k])) + # for vals that are non-null (or list if node consolidation was done) + tags = ( + {"k": k, "v": str(node[k])} + for k in node_tags & node.keys() + if isinstance(node[k], list) or pd.notna(node[k]) + ) for tag in tags: _ = SubElement(node_element, "tag", attrib=tag) diff --git a/osmnx/simplification.py b/osmnx/simplification.py index 7de7ee908..f5adba720 100644 --- a/osmnx/simplification.py +++ b/osmnx/simplification.py @@ -13,6 +13,7 @@ from shapely.geometry import Point from shapely.geometry import Polygon +from . import settings from . import stats from . import utils from . import utils_graph @@ -511,7 +512,7 @@ def _merge_nodes_geometric(G: nx.MultiDiGraph, tolerance: float) -> gpd.GeoSerie return gpd.GeoSeries(merged.geoms, crs=G.graph["crs"]) -def _consolidate_intersections_rebuild_graph( # noqa: PLR0912,PLR0915 +def _consolidate_intersections_rebuild_graph( # noqa: C901,PLR0912,PLR0915 G: nx.MultiDiGraph, tolerance: float, reconnect_edges: bool, # noqa: FBT001 @@ -563,7 +564,9 @@ def _consolidate_intersections_rebuild_graph( # noqa: PLR0912,PLR0915 # attach each node to its cluster of merged nodes. first get the original # graph's node points then spatial join to give each node the label of # cluster it's within. make cluster labels type string. - node_points = utils_graph.graph_to_gdfs(G, edges=False)[["geometry"]] + node_points = utils_graph.graph_to_gdfs(G, edges=False) + cols = set(node_points.columns).intersection(["geometry", *settings.useful_tags_node]) + node_points = node_points[list(cols)] gdf = gpd.sjoin(node_points, node_clusters, how="left", predicate="within") gdf = gdf.drop(columns="geometry").rename(columns={"index_right": "cluster"}) gdf["cluster"] = gdf["cluster"].astype(str) @@ -608,14 +611,24 @@ def _consolidate_intersections_rebuild_graph( # noqa: PLR0912,PLR0915 osmid = osmids[0] H.add_node(cluster_label, osmid_original=osmid, **G.nodes[osmid]) else: - # if cluster is multiple merged nodes, create one new node to - # represent them - H.add_node( - cluster_label, - osmid_original=str(osmids), - x=nodes_subset["x"].iloc[0], - y=nodes_subset["y"].iloc[0], - ) + # if cluster is multiple merged nodes, create one new node with + # attributes to represent them + node_attrs = { + "osmid_original": osmids, + "x": nodes_subset["x"].iloc[0], + "y": nodes_subset["y"].iloc[0], + } + for col in set(nodes_subset.columns).intersection(settings.useful_tags_node): + # get the unique non-null values (we won't add null attrs) + unique_vals = list(set(nodes_subset[col].dropna())) + if len(unique_vals) == 1: + # if there's 1 unique value for this attribute, keep that + # one value + node_attrs[col] = unique_vals[0] + elif len(unique_vals) > 1: + # if there are multiple unique values, keep all uniques + node_attrs[col] = unique_vals + H.add_node(cluster_label, **node_attrs) # calculate street_count attribute for all nodes lacking it null_nodes = [n for n, sc in H.nodes(data="street_count") if sc is None] From 056a53b66766a2c8518226816f26b4bdcd43c6f5 Mon Sep 17 00:00:00 2001 From: Geoff Boeing Date: Tue, 12 Mar 2024 12:43:45 -0700 Subject: [PATCH 3/7] don't create useless lat and lon node attributes when geometry is stored in x and y --- osmnx/io.py | 2 -- osmnx/projection.py | 6 ------ 2 files changed, 8 deletions(-) diff --git a/osmnx/io.py b/osmnx/io.py index 3817e730f..fc9dfe7a0 100644 --- a/osmnx/io.py +++ b/osmnx/io.py @@ -190,8 +190,6 @@ def load_graphml( default_node_dtypes = { "elevation": float, "elevation_res": float, - "lat": float, - "lon": float, "osmid": int, "street_count": int, "x": float, diff --git a/osmnx/projection.py b/osmnx/projection.py index f6461c040..187d17401 100644 --- a/osmnx/projection.py +++ b/osmnx/projection.py @@ -163,12 +163,6 @@ def project_graph( # STEP 1: PROJECT THE NODES gdf_nodes = utils_graph.graph_to_gdfs(G, edges=False) - # create new lat/lon columns to preserve lat/lon for later reference if - # cols do not already exist (ie, don't overwrite in later re-projections) - if "lon" not in gdf_nodes.columns or "lat" not in gdf_nodes.columns: - gdf_nodes["lon"] = gdf_nodes["x"] - gdf_nodes["lat"] = gdf_nodes["y"] - # project the nodes GeoDataFrame and extract the projected x/y values gdf_nodes_proj = project_gdf(gdf_nodes, to_crs=to_crs) gdf_nodes_proj["x"] = gdf_nodes_proj["geometry"].x From aaa5e8a60cc2983d00618d5631b21fa41887e4f5 Mon Sep 17 00:00:00 2001 From: Geoff Boeing Date: Tue, 12 Mar 2024 15:54:13 -0700 Subject: [PATCH 4/7] update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fc9f006ce..3a7f258f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,9 @@ Read the v2 [migration guide](https://github.com/gboeing/osmnx/issues/1123) - 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) +- add OSM junction and railway tags to the default settings.useful_tags_node (#1144) +- stop creating useless lat and lon node attributes when projecting a graph (#1144) - make optional function parameters keyword-only throughout package (#1134) - make dist function parameters required rather than optional throughout package (#1134) - make which_result function parameter consistently able to accept a list throughout package (#1113) From a6eda887d9efbb5c3df34ec2ca2978e3bee7dddf Mon Sep 17 00:00:00 2001 From: Geoff Boeing Date: Tue, 12 Mar 2024 17:40:41 -0700 Subject: [PATCH 5/7] fix comment --- osmnx/simplification.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osmnx/simplification.py b/osmnx/simplification.py index f5adba720..4068e1204 100644 --- a/osmnx/simplification.py +++ b/osmnx/simplification.py @@ -622,8 +622,7 @@ def _consolidate_intersections_rebuild_graph( # noqa: C901,PLR0912,PLR0915 # get the unique non-null values (we won't add null attrs) unique_vals = list(set(nodes_subset[col].dropna())) if len(unique_vals) == 1: - # if there's 1 unique value for this attribute, keep that - # one value + # if there's 1 unique value for this attribute, keep it node_attrs[col] = unique_vals[0] elif len(unique_vals) > 1: # if there are multiple unique values, keep all uniques From 9de6b89252e3ab91f9cffcffba16616dca9098dc Mon Sep 17 00:00:00 2001 From: Geoff Boeing Date: Wed, 13 Mar 2024 12:16:09 -0700 Subject: [PATCH 6/7] clarify projection warning now that we are not adding lat-lon attrs --- osmnx/_osm_xml.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/osmnx/_osm_xml.py b/osmnx/_osm_xml.py index c18f95ff6..304986e2d 100644 --- a/osmnx/_osm_xml.py +++ b/osmnx/_osm_xml.py @@ -212,17 +212,14 @@ def _save_graph_xml( else: gdf[col] = gdf[col].fillna(value) - # warn user if graph is projected then remove lat/lon gdf_nodes columns if - # they exist, as x/y cols will be saved as lat/lon node attributes instead + # warn user if graph is projected if projection.is_projected(G.graph["crs"]): msg = ( - "Graph should be unprojected: the existing lat-lon node attributes will " - "be discarded and the projected x-y coordinates will be saved as lat-lon " - "node attributes instead. Project your graph back to lat-lon to avoid this." + "Graph should be unprojected: the existing projected x-y coordinates " + "will be saved as lat-lon node attributes. Project your graph back to " + "lat-lon to avoid this." ) warn(msg, category=UserWarning, stacklevel=2) - for col in set(gdf_nodes.columns) & {"lat", "lon"}: - gdf_nodes = gdf_nodes.drop(columns=[col]) # transform nodes gdf to meet OSM XML spec # 1) reset index (osmid) then rename osmid, x, and y columns From 06c4ff048c9ef5a9aa3ff18fb1537def850c6a71 Mon Sep 17 00:00:00 2001 From: Geoff Boeing Date: Wed, 13 Mar 2024 12:35:10 -0700 Subject: [PATCH 7/7] update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a7f258f0..fe9f20ce3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,7 @@ Read the v2 [migration guide](https://github.com/gboeing/osmnx/issues/1123) - 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) - add OSM junction and railway tags to the default settings.useful_tags_node (#1144) -- stop creating useless lat and lon node attributes when projecting a graph (#1144) +- fix graph projection creating useless lat and lon node attributes (#1144) - make optional function parameters keyword-only throughout package (#1134) - make dist function parameters required rather than optional throughout package (#1134) - make which_result function parameter consistently able to accept a list throughout package (#1113)