Skip to content

Add function to animate graph routes#1285

Open
nkleinbaer wants to merge 5 commits into
gboeing:mainfrom
nkleinbaer:1187_animate_route
Open

Add function to animate graph routes#1285
nkleinbaer wants to merge 5 commits into
gboeing:mainfrom
nkleinbaer:1187_animate_route

Conversation

@nkleinbaer

Copy link
Copy Markdown

Opening a PR to implement feature proposed in #1187. Adds a new function animate_graph_route that is analogous to plot_graph_route, but shows a edge-by-edge animation instead of a static image.

In 59fd139 I simply copied the existing plot_graph_route function and modified it to create an animation. Since this created duplication of code shared by the two funcs (i.e. plotting the graph itself and origin/destination nodes) I then added a 52ffb46 to refactor this out. Wanted to keep that as a separate commit commit so it's easy to drop per your advice (thinking perhaps you may want to avoid touching existing functionality?).

For testing just added a single call to the new function in test_routing, following from what I saw done for plot_graph_route. Can add more tests if desired.

Haven't updated the changelog yet, not sure if I should put this under the 2.0.2 section or a new section.

Example usage (adapted from this example):

import osmnx as ox

place = "Piedmont, California, USA"
G = ox.graph.graph_from_place(place, network_type="drive")

# impute missing edge speeds and calculate free-flow travel times
G = ox.routing.add_edge_speeds(G)
G = ox.routing.add_edge_travel_times(G)

# calculate 3 shortest paths, minimizing travel time
w = "travel_time"
orig, dest = list(G)[10], list(G)[-10]
route1 = ox.routing.shortest_path(G, orig, dest, weight=w)
orig, dest = list(G)[0], list(G)[-1]
route2 = ox.routing.shortest_path(G, orig, dest, weight=w)
orig, dest = list(G)[-100], list(G)[100]
route3 = ox.routing.shortest_path(G, orig, dest, weight=w)

# animate routes
fig, ax = ox.plot.animate_graph_route(G, route1, orig_dest_size=0, node_size=0, save=True, show=True, filepath='images/animation.mp4')
fig, ax = ox.plot.animate_graph_route(G, route2, orig_dest_size=0, node_size=0, save=True, show=True, filepath='images/animation.gif')
fig, ax = ox.plot.animate_graph_route(G, route3, orig_dest_size=0, node_size=0, save=True, show=True, filepath='images/animation.mov')

@nkleinbaer nkleinbaer mentioned this pull request Mar 13, 2025
3 tasks
@codecov

codecov Bot commented Mar 13, 2025

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.46%. Comparing base (c6efebf) to head (d5801e8).
⚠️ Report is 400 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1285      +/-   ##
==========================================
+ Coverage   98.44%   98.46%   +0.02%     
==========================================
  Files          24       24              
  Lines        2383     2417      +34     
==========================================
+ Hits         2346     2380      +34     
  Misses         37       37              

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

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment thread osmnx/plot.py Outdated
Comment thread osmnx/plot.py Outdated
Comment thread osmnx/plot.py Outdated
Comment thread osmnx/plot.py
Comment on lines +380 to +388
G: nx.MultiDiGraph,
route: list[int],
*,
route_color: str = "r",
route_linewidth: float = 4,
route_alpha: float = 0.5,
orig_dest_size: float = 100,
ax: Axes | None = None,
**pg_kwargs: Any, # noqa: ANN401

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Does the user need more control than this? Animation speed? Repetition? Would fine-grain control be more easily exposed via keyword arguments to pass to matplotlib? Or?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Yeah that makes sense, IMO it's worth adding the two you mentioned (speed and repetition) as explicitly defined/documented arguments here, then additionally allow passing arbitrary kwargs to matplotlib for more advanced/obscure things.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

We should keep the function signature/arguments as parsimonious and streamlined as possible. If these are straightforward kwargs to pass directly to matplotlib, that's probably the best solution.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

The issue I'm running into with this is that we can only gobble up one dict of arbitrary kwargs, but then there are several places in this function where they may need to be passed: some may need to go to plot_graph (by way of _prepare_graph_route_plot), others may need to go to the constructor of FuncAnimation, others may need to go to Animation.save's savefig_kwargs argument, and finally some may need to be passed directly to Animation.save. So I feel like I need to split them all out to avoid passing unexpected kwargs in the wrong spot ... something like this nkleinbaer@904ecda ... but that feels kinda hacky and difficult to maintain (not to mention I haven't tested out that passing any of these kwargs actually works as expected, that's just a draft). What are your thoughts?

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

@nkleinbaer perhaps each of the constant kwarg dicts you define in that commit at lines 404-460 could instead be individual function arguments of type dict. Then the user could pass individual kwarg dicts for each destination function.

Comment thread osmnx/__init__.py Outdated
@gboeing

gboeing commented Mar 26, 2025

Copy link
Copy Markdown
Owner

@jabahm you expressed interest in animation in #1187. Do you have any comments/feedback for this PR?

@olivier-be

olivier-be commented Jun 7, 2025

Copy link
Copy Markdown

Hello, I think this feature should be implemented, it works well and looks good. I have just found one thing that could be improved by adding support for Jupyter Lab, with in the animate_graph_route function:

if pg_kwargs.get("html_animation", True):
   From IPython.display import HTML
    HTML(anim.to_html5_video())

Or return the anim variable so we can do it outside.

Another possible idea is to be able to animate multiple routes at the same time.

Sorry if I'm not using GitHub correctly.

@gboeing

gboeing commented Jun 24, 2025

Copy link
Copy Markdown
Owner

@olivier-be thanks for your feedback. I agree we should make this useful for Jupyter users. We won't want to add an ipython dependency to the package to support it, but we could return the animation object to the caller, similar to how the static plotting function return fig, ax tuples to the caller.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants