gboeing / osmnx

OSMnx is a Python package to easily download, model, analyze, and visualize street networks and other geospatial features from OpenStreetMap.
https://osmnx.readthedocs.io
MIT License
4.84k stars 821 forks source link

Feature request: truncate_by_nearest_edge while generating graph from point #1207

Closed otepencelik closed 1 week ago

otepencelik commented 1 month ago

Contributing guidelines

Documentation

Existing issues

What problem does your feature proposal solve?

When generating a graph from a gps coordinate, if an edge of the graph crosses the bounding box but none of its nodes are inside the bounding box, the edge is not included in the resulting graph. This could turn out to be useful in some scenarios. For example, if we are trying to query OSM with a location from a vehicle going over a long bridge, the bridge edge should be included in the resulting graph as it is the nearest edge to the query point, but often it won't end up in the graph since the distance between the nodes are too long. This also happens for some highways that contain long edges.

What is your proposed solution?

I don't have an implemented solution but I think that this could be a useful feature.

What alternatives have you considered?

The alternative is using a big enough bounding box which is computationally expensive.

Additional context

The context is provided in the proposal.

gboeing commented 1 month ago

Thanks for using OSMnx and suggesting this.

I think I disagree with this proposal on theoretical grounds. A graph (or the network itself) is a set of nodes which may (or may not) be linked to each other by a set of edges. The edges themselves are either ordered pairs (for a directed graph) or unordered tuples (for an undirected graph) of nodes. That is, the nodes have primacy as the object of the model and the edges exist in reference to the nodes.

That said, I think that:

if an edge of the graph crosses the bounding box but none of its nodes are inside the bounding box, the edge is not included in the resulting graph

is therefore the correct behavior. If the bounding box represents the spatial bounds of the model, and if an edge is a pair of nodes, then at least one node must exist within those bounds.

if we are trying to query OSM with a location from a vehicle going over a long bridge, the bridge edge should be included in the resulting graph as it is the nearest edge to the query point, but often it won't end up in the graph since the distance between the nodes are too long. This also happens for some highways that contain long edges.

OSMnx does accommodate this. All of the graph module's graph_from_whatever functions offer a truncate_by_edge parameter. If False, then both endpoint nodes must be within the query bounds for an edge to be retained in the model. But if True, then just one node must be. This is useful when modeling a highway or bridge with just one endpoint in the query area, as that long edge will be retained even if its other endpoint falls outside the area.

GCRVL commented 3 weeks ago

I understand the design philosophy, but ultimately it deprecates the usefulness of OSMNX (which is awesome, don't get me wrong). Ultimately, the benefits listed in the overview of the documentation are hindered by it. You cannot model a street network if roads that pass through a corner with nodes outside of the bounding box are not rendered.

If I want to generate a map of a city highways/railroads or whatever passing through the bounding box, with two nodes outside of the bounding box, are not visible. So OSMNX cannot be used for generating accurate maps as it stands now.

otepencelik commented 3 weeks ago

Thank you so much for your prompt response and this amazing library @gboeing. I fully agree with @GCRVL. I would also like to point out that I was not questioning the current design and proposing a change to it, I was just trying to suggest this as an optional feature for specific use cases that might benefit from it, just like the truncate_by_edge parameter.

I now realize that my suggestion of truncate_by_nearest_edge only addresses my specific use case, and a more generic solution that checks for the intersection of each edge with the bounding box polygon would be able to address all kinds of similar issues as well, as mentioned by @GCRVL.

gboeing commented 3 weeks ago

You cannot model a street network if roads that pass through a corner with nodes outside of the bounding box are not rendered.

@GCRVL Yes, you can, but it may take a few extra lines of code to generate the model you want. Could you provide an example code snippet to reproduce this limitation? I suspect it's feasible.

So OSMNX cannot be used for generating accurate maps as it stands now.

@GCRVL Just as a side note, generating maps is not the primary goal of OSMnx. Rather, it aims to generate accurate graph models (in the spirit of spatial graph theory mentioned above), and also allows you to visualize those graphs.

gboeing commented 3 weeks ago

I now realize that my suggestion of truncate_by_nearest_edge only addresses my specific use case, and a more generic solution that checks for the intersection of each edge with the bounding box polygon would be able to address all kinds of similar issues as well

@otepencelik Yes, OSMnx currently lets you do this in a few lines of code. This is what I meant when I told @GCRVL in the previous comment: "Yes, you can, but it may take a few extra lines of code to generate the model you want." For example, you can get a buffered graph then retain only the nodes of edges that intersect the desired study area:

import osmnx as ox

# get a graph the usual way
place = "Piedmont, CA, USA"
G0 = ox.graph.graph_from_place(place, network_type="drive")
len(G0)  # 352

# OR loosen the study area constraint via truncate_by_edge
G1 = ox.graph.graph_from_place(place, network_type="drive", truncate_by_edge=True)
len(G1)  # 394

# OR retain all nodes with at least 1 incident edge intersecting study area
geom = ox.geocoder.geocode_to_gdf(place)["geometry"].iloc[0]
geom_proj, crs_proj = ox.projection.project_geometry(geom)
geom_buff, _ = ox.projection.project_geometry(geom_proj.buffer(500), crs=crs_proj, to_latlong=True)
G2 = ox.graph.graph_from_polygon(geom_buff, network_type="drive")
edges = ox.convert.graph_to_gdfs(G2, nodes=False)
nodes_keep = edges[edges.intersects(geom)].reset_index()[["u", "v"]].stack().unique()
G2.remove_nodes_from(set(G2.nodes) - set(nodes_keep))
len(G2)  # 395
gboeing commented 3 weeks ago

With #1214 you can do this even more simply:

import osmnx as ox

# retain all nodes with at least 1 incident edge intersecting study area
geom = ox.geocoder.geocode_to_gdf("Piedmont, CA, USA").iloc[0]["geometry"]
G = ox.graph.graph_from_polygon(ox.utils_geo.buffer_geometry(geom, 500), network_type="drive")
edges = ox.convert.graph_to_gdfs(G, nodes=False)
keep = edges[edges.intersects(geom)].reset_index()[["u", "v"]].stack().unique()
G.remove_nodes_from(set(G.nodes) - set(keep))
gboeing commented 1 week ago

Resolved by #1214 (and the example code above).