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

frontend: python frontend for Mesa-Geo #157

Closed
wang-boyu opened this issue Jul 28, 2023 · 21 comments
Closed

frontend: python frontend for Mesa-Geo #157

wang-boyu opened this issue Jul 28, 2023 · 21 comments
Labels
enhancement Release notes label help wanted
Milestone

Comments

@wang-boyu
Copy link
Member

wang-boyu commented Jul 28, 2023

What's the problem this feature will solve?

Mesa v2.1 has introduced a mesa.experimental namespace that contains JupyterViz for model visualization in Jupyter notebooks (see projectmesa/mesa#1698 and projectmesa/mesa#1726).

It would be great to similarly have a corresponding set of widgets to visualize GeoSpaces in Jupyter notebooks and web apps too.

Describe the solution you'd like

Either extend the JupyterViz class or have a new class for Mesa-Geo. Currently Mesa uses the Solara framework to create UI components.

Additional context

@wang-boyu wang-boyu added this to the v0.6.0 milestone Jul 28, 2023
@wang-boyu wang-boyu changed the title frontend: Jupyter widgets for GeoSpace frontend: python frontend for Mesa-Geo Jul 28, 2023
@wang-boyu wang-boyu added the enhancement Release notes label label Aug 8, 2023
@wang-boyu wang-boyu modified the milestones: v0.6.0, v0.7.0 Sep 12, 2023
@wang-boyu
Copy link
Member Author

Hi @Corvince, will you be interested in taking a look at this? @tpike3 and I are probably going to talk about Mesa & Mesa-Geo in an upcoming conference in early Nov. We're thinking to create a tutorial using one of our example models.

Sorry about this short notice. But having a Solora frontend for Mesa-Geo would be super useful : )

@Corvince
Copy link
Contributor

Sorry, I thought I had already answered here. Yes, I would be interested to implement a solara-based frontend. In fact I am working in this right now

@wang-boyu
Copy link
Member Author

Great! Looking forward to your implementation. This will be very helpful.

@Corvince
Copy link
Contributor

An update on things I tried so far:

I wanted to leverage Ipyleaflet to have a leaflet map that is similar to what is included currently with the old frontend. After some troubles converting the agents to a compatible GeoJSON format (solara had some issues), I realized this is not needed and we can just pass in a GeoDataFrame.

We can get that easily with

gdf = model.space.get_agents_as_GeoDataFrame().to_crs("epsg:4326")

With this we can create a ipyleaflet map with something like this

import ipyleaflet

center_default = (53.2305799, 6.5323552)
zoom_default = 5

ipyleaflet.Map(
    center=center_default,
    zoom=zoom_default,
    layers=[
        ipyleaflet.TileLayer(),
        ipyleaflet.GeoData(
            geo_dataframe=gdf,
            style={
                "color": "blue",
                "fillColor": "#3366cc",
                "opacity": 0.05,
                "weight": 1.9,
                "dashArray": "2",
                "fillOpacity": 0.6,
            },
        ),
    ],
)

The problematic thing is coloring the agents based on some property. In leaflet we could define the color in terms of its feature with a function that maps a feature to a color. But this doesn't seem to be possible with IPyleaflet. We could probably create different layers for each color, but I haven't tried.

The other possibility would be to leverage Altair. Here we can define the colors decleratively like so:

import altair as alt

alt.Chart(data=gdf).mark_geoshape().encode(color="atype:O")

However, including a basemap is not super well supported, but might be possible, see vega/vega-lite#5758

And the output needs some finetuning.

But I won't be able to investigate this further until beginning of November. So whats the current timeline for the tutorial @wang-boyu ?

@wang-boyu
Copy link
Member Author

I'm so glad that you're looking into this! Thank you! Was worried about it in the past few days.

There is a dev meeting tomorrow and I'll discuss with @tpike3 about the tutorial. After that I'll post an update here.

@tpike3
Copy link
Member

tpike3 commented Oct 14, 2023

The tutorial is at the beginning of November (Nov2nd or 3rd) @Corvince would you have a branch you can push so I can just build off what you have already done?

@Corvince
Copy link
Contributor

Unfortunately not, but it boils down to the code snippets I posted. I can try to assist if you have questions, but I don't have access to my laptop until Nov.

But both approaches should be possible to wrap in a function like the current grid or space views that accept model and agent_portrayal as their arguments. From there you can generate the geodataframe and return either the solara leaflet or Altair figure

@wang-boyu
Copy link
Member Author

Since we're not really ready for the tutorial, we're going to skip the CSSSA session this year (@tpike3 will still be there to give a Mesa tutorial and may possibly mention Mesa-Geo briefly, but not a full tutorial for Mesa-Geo).

The next window is the online tutorial series from CSSSA (link to the Mesa online tutorial by @tpike3: youtu.be/8P5P7NpCx5o) which is currently scheduled at Feb 2024 for Mesa-Geo. Would be great if we can have the feature and the tutorial ready by then : )

@Corvince
Copy link
Contributor

That sounds doable!

@wang-boyu
Copy link
Member Author

I looked at the Solara frontend in Mesa (long overdue) and am wondering whether a dedicated frontend for Mesa-Geo is needed at all, since we can simple feed a matplotlib figure (i.e., solara.FigureMatplotlib) into JupyterViz.

For example users could define such a function in this way:

def space_drawer(model):
    agents_gdf = model.space.get_agents_as_GeoDataFrame()
    fig, ax = plt.subplots()

    # users do some plotting here with geopandas, for example:
    agents_gdf.plot(column="some_col", ax=ax)

    # similarly for raster layers
    for layer in model.space.layers:
        if isinstance(layer, RasterLayer):
            ... # plot it using rasterio or matplotlib or any other library that is based on matplotlib

    return solara.FigureMatplotlib(fig)

The benefits are that, for the users, they don't need to learn how to make plots in Mesa (e.g., is it "color" or "Color" in agent_portrayal). They only need to know how to use the visualization tools they choose. For us developers, we don't need to maintain agent_portrayal and how to integrate it with backend plotting libraries.

The catch here is that JupyterViz expects space_drawer to be space_drawer(model, agent_portrayal), not space_drawer(model).

On a side note, if Mesa agents could be retrieved as dataframe in a similar way, then probably agent_portrayal is not need in Mesa either. Currently there is get_agent_vars_dataframe but it would need agent reporters, which is for something else.

Any thoughts on this?

@rht
Copy link
Contributor

rht commented Nov 4, 2023

I looked at the Solara frontend in Mesa (long overdue) and am wondering whether a dedicated frontend for Mesa-Geo is needed at all, since we can simple feed a matplotlib figure (i.e., solara.FigureMatplotlib) into JupyterViz.

That's a good idea. If the redraws performance is reasonable (with PNG output, projectmesa/mesa#1819), this should do. Then the ipyleaflet implementation would be for performance optimization.

On a side note, if Mesa agents could be retrieved as dataframe in a similar way, then probably agent_portrayal is not need in Mesa either. Currently there is get_agent_vars_dataframe but it would need agent reporters, which is for something else.

agent_portrayal is needed so that user doesn't have to write their own space_drawer, for most use cases, in that they just have to declaratively specify the params.
But it is possible to instead define a space_drawer function preparer that can be imported, that takes in agent_portrayal, and returns a space_drawer(model). This seems to be more general.

@wang-boyu
Copy link
Member Author

Made a simple demo for this with the GeoSchellingPoints model: https://github.com/wang-boyu/mesa-examples/blob/gis%2Fsolara-frontend/gis/geo_schelling_points/app.ipynb

@rht
Copy link
Contributor

rht commented Nov 5, 2023

What functionalities are missing in that demo in comparison to the mesa-viz-tornado version?

@wang-boyu
Copy link
Member Author

wang-boyu commented Nov 5, 2023

What functionalities are missing in that demo in comparison to the mesa-viz-tornado version?

Good question! One thing that I didn't do in the demo is to have some popup properties, so that users can click an agent on the map and see some descriptions. Not sure how this will work with Solara.

It is mainly a different way of doing things with these two approaches. Previously in agent_portrayal we will do something like this:

# `agent` is an object of type `GeoAgent`
if agent.red_cnt > agent.blue_cnt:
    portrayal["color"] = "Red"
elif agent.red_cnt < agent.blue_cnt:
    portrayal["color"] = "Blue"
else:
    portrayal["color"] = "Grey"

whereas now we need to do

# `region_agents` is a GeoDataFrame
region_agents["is_red"] = region_agents["red_cnt"] > region_agents["blue_cnt"]

# Then figure out the color palette you want with geopandas plot, or simply use the default (do nothing)
color_map = mpl.colors.ListedColormap(plt.cm.Set2.colors[:2])
region_agents.plot(column="is_red", cmap=color_map, alpha=0.5, ax=space_ax)

Operating at the GeoDataFrame level may appear simple in this demo. But back in agent_portrayal we could do really complicated if-else conditions. For example if the color depends on neighbors statuses. But the GeoDataFrame approach may not work in this case, because the data is already extracted as dataframe, and we wouldn't be able to know which rows (agents) are the neighbors of other rows (agents).

In this case we may need to go back to the geoagent code, and think about how to write what we want as an attribute, so that it could be exported as a column of the GeoDataFrame. In fact this sounds a good practice even for the agent_portrayal approach, to avoid complex if-else conditions in the portrayal function.

@rht
Copy link
Contributor

rht commented Nov 6, 2023

In this case we may need to go back to the geoagent code, and think about how to write what we want as an attribute, so that it could be exported as a column of the GeoDataFrame. In fact this sounds a good practice even for the agent_portrayal approach, to avoid complex if-else conditions in the portrayal function.

That makes sense, in that the agent_portrayal should be about declaring the output detail. The DF output would constraint the user to do this.

I think it is sufficient to merge the Solara demo, with a documentation that says the rendering is not clickable, and that the clickable version is still in the work, pointing to this issue.

We should discuss about removing the agent_portrayal argument from the custom space drawer in the next dev meeting. I am on board with it. It reduces unnecessary hardcoded structure that the user has to learn. It's like having a web browser with as few chrome components as possible, and more customizable HTML elements instead.

@wang-boyu
Copy link
Member Author

We should discuss about removing the agent_portrayal argument from the custom space drawer in the next dev meeting. I am on board with it. It reduces unnecessary hardcoded structure that the user has to learn.

How about making it optional... Now I think about it, removing it would need users to know how to use pandas to manipulate and plot dataframes, whereas agent_portrayal requires basic python knowledge. For developers, removing it definitely lightens the burden. But yeah let's talk more in the dev meeting.

@maltevogl
Copy link

I would be interested in this feature a lot. Is there any news on the progress?

@EwoutH
Copy link
Member

EwoutH commented Apr 26, 2024

I think we're stabilizing JupyterViz in main Mesa first (see projectmesa/mesa#2090), then build the mesa-geo on it. Except if @wang-boyu has an other vision on it.

@wang-boyu
Copy link
Member Author

@tpike3 has been working on it with a recent PR #199 as well as an example projectmesa/mesa-examples#117. But they are not merged yet.

@maltevogl
Copy link

Thanks for the feedback, I ll have a look at the examples above.

@wang-boyu
Copy link
Member Author

GeoJupyterViz was implemented in #212. Closing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement Release notes label help wanted
Projects
None yet
Development

No branches or pull requests

6 participants