diff --git a/osmnx/_osm_xml.py b/osmnx/_osm_xml.py index 26fb5bdab..89c549b77 100644 --- a/osmnx/_osm_xml.py +++ b/osmnx/_osm_xml.py @@ -23,6 +23,7 @@ import networkx as nx import pandas as pd +from . import projection from . import settings from . import utils from . import utils_graph @@ -160,7 +161,7 @@ def _save_graph_xml( Parameters ---------- G - Unsimplified graph to save as an OSM XML file. + Unsimplified, unprojected graph to save as an OSM XML file. filepath Path to the saved file including extension. If None, use default `settings.data_folder/graph.osm`. @@ -211,6 +212,18 @@ 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 + 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." + ) + 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 # 2) round lat/lon coordinates diff --git a/osmnx/io.py b/osmnx/io.py index 6cd000193..3817e730f 100644 --- a/osmnx/io.py +++ b/osmnx/io.py @@ -270,14 +270,14 @@ def save_graph_xml( This function merges graph edges such that each OSM way has one entry in the XML output, with the way's nodes topologically sorted. `G` must be unsimplified to save as OSM XML: otherwise, one edge could comprise - multiple OSM ways, making it impossible to properly group edges by way. + multiple OSM ways, making it impossible to group and sort edges in way. `G` should also have been created with `ox.settings.all_oneway=True` for this function to behave properly. Parameters ---------- G - Unsimplified graph to save as an OSM XML file. + Unsimplified, unprojected graph to save as an OSM XML file. filepath Path to the saved file including extension. If None, use default `settings.data_folder/graph.osm`. diff --git a/tests/test_osmnx.py b/tests/test_osmnx.py index d17a83476..9c71cbd7f 100644 --- a/tests/test_osmnx.py +++ b/tests/test_osmnx.py @@ -164,7 +164,7 @@ def test_osm_xml() -> None: Path.unlink(Path(temp_filename)) - # test .osm xml saving + # test OSM xml saving G = ox.graph_from_point(location_point, dist=500, network_type="drive", simplify=False) fp = Path(ox.settings.data_folder) / "graph.osm" ox.io.save_graph_xml(G, filepath=fp, way_tag_aggs={"lanes": "sum"}) @@ -180,14 +180,20 @@ def test_osm_xml() -> None: default_overpass_settings = ox.settings.overpass_settings ox.settings.overpass_settings += '[date:"2023-04-01T00:00:00Z"]' point = (39.0290346, -84.4696884) - G = ox.graph_from_point(point, dist=500, dist_type="bbox", network_type="drive", simplify=True) - with pytest.raises(ox._errors.GraphSimplificationError): - ox.io.save_graph_xml(G) G = ox.graph_from_point(point, dist=500, dist_type="bbox", network_type="drive", simplify=False) - nx.set_node_attributes(G, 0, name="uid") ox.io.save_graph_xml(G) _ = etree.parse(fp, parser=parser) # noqa: S320 - G = ox.graph_from_xml(fp) # issues UserWarning + + # raise error if trying to save a simplified graph + with pytest.raises(ox._errors.GraphSimplificationError): + ox.io.save_graph_xml(ox.simplification.simplify_graph(G)) + + # save a projected/consolidated graph as OSM XML + Gc = ox.simplification.consolidate_intersections(ox.projection.project_graph(G)) + nx.set_node_attributes(Gc, 0, name="uid") + ox.io.save_graph_xml(Gc, fp) # issues UserWarning + Gc = ox.graph.graph_from_xml(fp) # issues UserWarning + _ = etree.parse(fp, parser=parser) # noqa: S320 # restore settings ox.settings.overpass_settings = default_overpass_settings