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

add endpoint_attrs param to simplify_graph to flexibly relax strictness #1117

Merged
merged 5 commits into from
Jan 30, 2024

Conversation

gboeing
Copy link
Owner

@gboeing gboeing commented Jan 26, 2024

Resolves #625

This PR:

  • adds endpoint_attrs param to simplification.simplify_graph function to flexibly relax strictness
  • deprecates strict param in simplification.simplify_graph function in favor of new endpoint_attrs param
  • improves docstrings and comments in the simplification module

Per the new deprecation message:

The strict parameter has been deprecated and will be removed in the v2.0.0 release. Use the endpoint_attrs parameter instead to relax simplification strictness. For example, endpoint_attrs=None reproduces the old strict=True behvavior and endpoint_attrs=['osmid'] reproduces the old strict=False behavior.

Copy link

codecov bot commented Jan 26, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Comparison is base (b814772) 97.86% compared to head (2e39e19) 97.86%.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #1117   +/-   ##
=======================================
  Coverage   97.86%   97.86%           
=======================================
  Files          28       28           
  Lines        2531     2534    +3     
=======================================
+ Hits         2477     2480    +3     
  Misses         54       54           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@gboeing
Copy link
Owner Author

gboeing commented Jan 26, 2024

@csebastiao would you like to test this out with a specific case?

@csebastiao
Copy link

@gboeing I have tested it on my side on the two examples of the Toy graph and Copenhagen custom bicycle attribute and both are returning the expected behaviors.

It's all good for me.

@gboeing
Copy link
Owner Author

gboeing commented Jan 26, 2024

Thanks. Can you provide a code snippet for testing your use case?

@csebastiao
Copy link

Oh sure sorry about that, here is the one for the Toy Graph made by hand:

import networkx as nx
import shapely

import osmnx as ox

G = nx.Graph()
G.add_node(1, x=1, y=1)
G.add_node(2, x=2, y=1)
G.add_node(3, x=2.5, y=1.5)
G.add_node(4, x=3, y=2.5)
G.add_node(5, x=3.5, y=3.5)
G.add_node(6, x=3, y=4)
G.add_node(7, x=3, y=5)
G.add_node(8, x=3.5, y=5.5)
G.add_node(9, x=3.5, y=6.5)
G.add_node(10, x=4, y=7)
G.add_node(11, x=5, y=8)
G.add_node(12, x=6, y=8)
G.add_node(13, x=6.5, y=8.5)
G.add_node(14, x=7, y=9)
G.add_node(15, x=7.5, y=8.5)
G.add_node(16, x=8, y=8)
G.add_node(17, x=7.5, y=7.5)
G.add_node(18, x=7, y=7)
G.add_node(19, x=6.5, y=7.5)
# add length and osmid just for the osmnx function to work
for i in range(1, 19):
    G.add_edge(i, i + 1, length=1, osmid=i)
G.add_node(20, x=4, y=4)
G.add_node(21, x=4, y=5)
G.add_edge(5, 20, length=1, osmid=20)
G.add_edge(20, 21, length=1, osmid=21)
G.add_edge(21, 8, length=1, osmid=22)
G.add_edge(19, 12, length=1, osmid=23)
# give three value of color to see the discrimination for an attribute
for i in range(2, 8):
    G.edges[i, i + 1]["color"] = 1
G.edges[1, 2]["color"] = 2
G.edges[5, 20]["color"] = 1
G.edges[20, 21]["color"] = 1
G.edges[21, 8]["color"] = 1
for i in range(8, 11):
    G.edges[i, i + 1]["color"] = 3
for i in range(11, 19):
    G.edges[i, i + 1]["color"] = 2
G.edges[19, 12]["color"] = 2
G = nx.MultiDiGraph(G)
# add crs for the ox_plot_graph to work
G.graph["crs"] = "epsg:4326"
ec = ox.plot.get_edge_colors_by_attr(G, "color", cmap="Set1")
ox.plot_graph(
    G,
    figsize=(12, 8),
    bgcolor="w",
    node_color="black",
    node_size=30,
    edge_color=ec,
    edge_linewidth=3,
)
G_simple = ox.simplify_graph(G, endpoint_attrs=["color"])
ec_s = ox.plot.get_edge_colors_by_attr(G_simple, "color", cmap="Set1")
ox.plot_graph(
    G_simple,
    figsize=(12, 8),
    bgcolor="w",
    node_color="black",
    node_size=30,
    edge_color=ec_s,
    edge_linewidth=3,
)

Which can be asserted with

assert len(G_simple) == len(ox.simplify_graph(G)) + 2

One way to see it on an actual OSMnx graph is with this basic example:

import osmnx as ox

protected_dict = {}
protected_dict["sidewalk:left:bicycle"] = "yes"
protected_dict["sidewalk:left:right"] = "yes"
protected_dict["cycleway:left"] = ["shared_lane", "shared_busway", "track"]
protected_dict["cycleway:right"] = ["shared_lane", "shared_busway", "track"]
protected_dict["cycleway:both"] = "lane"
protected_dict["cycleway"] = ["shared_lane", "shared_busway", "opposite_lane", "opposite"]
protected_dict["bicycle"] = ["designated", "yes", "official", "use_sidepath"]
protected_dict["highway"] = ["cycleway", "bridleway"]
protected_dict["cyclestreet"] = "yes"
protected_dict["bicycle_road"] = "yes"
for val in protected_dict:
    if val not in ox.settings.useful_tags_way:
        ox.settings.useful_tags_way += [val]
G = ox.graph_from_place("Frederiksberg Municipality, Denmark", simplify=False)
for edge in G.edges:
    for key in list(protected_dict.keys()):
        if key in list(G.edges[edge].keys()):
            if G.edges[edge][key] in protected_dict[key]:
                G.edges[edge]["cycling"] = 1
                break
            else:
                G.edges[edge]["cycling"] = 0
        else:
            G.edges[edge]["cycling"] = 0
G_s = ox.simplify_graph(G, endpoint_attrs=None)
G_ns = ox.simplify_graph(G, endpoint_attrs=["osmid"])
G_attr = ox.simplify_graph(G, endpoint_attrs=["cycling"])
ec = ox.plot.get_edge_colors_by_attr(G_attr, "cycling", cmap="RdYlGn")
ox.plot_graph(
    G_attr,
    figsize=(12, 8),
    bgcolor="w",
    node_color="black",
    node_size=10,
    edge_color=ec,
    edge_linewidth=1,
)

To take a specific node (even thought with the ever-updating OSM I don't know if the exact values will stay) one can look at the node 1262273553:

G_attr.edges(1262273553, data=True)

It only exists when discriminating with the cycling (or osmid) attribute:

assert 1262273553 not in G_s
assert 1262273553 in G_attr

This should be the case as there are only two edges (1262273553, 9740065276, 0) and (1262273553, 9917054947, 0) with a different value for the cycling attribute, because for the latter we have 'highway': 'cycleway' and 'bicycle': 'designated' and for the former 'highway': 'footway'.

@EwoutH
Copy link
Contributor

EwoutH commented Jan 29, 2024

endpoint_attrs looks very useful, thanks for working on this!

@gboeing
Copy link
Owner Author

gboeing commented Jan 30, 2024

I believe this is ready to merge.

Copy link
Contributor

@EwoutH EwoutH left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Few minor comments. Really like the explicit numbered rules in the comments, helps a lot with understanding the code!

CHANGELOG.md Show resolved Hide resolved
osmnx/simplification.py Show resolved Hide resolved
@gboeing gboeing merged commit 99c55e1 into main Jan 30, 2024
10 checks passed
@gboeing gboeing deleted the simplify branch January 30, 2024 19:55
@gboeing
Copy link
Owner Author

gboeing commented Mar 13, 2024

Note that #1145 additionally adds a node_attrs_include param to the simplify_graph function to further flexibly relax graph simplification strictness. It also renames the endpoint_attrs param to edge_attrs_differ for consistent and clear naming, given the new param.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Customizable Graph Simplification
3 participants