Search code examples
pythonhtmlplotlygeojsonfolium

Loop over geoJson features and add plotly graph in popup html string to each


I've got a geoJson file with a bunch of features which I'm trying to display on an interactive folium map and I'm trying to add a plotly graph that pops up when you click on a polygon. At this moment I already have a folder with all plotly graphs for each city, 'currentWorkingDirectory/graphs/CityName.HTML'. I also have the interactive folium map with the different polygons which I can hover over or click for a popup.Current popup for poly

Now I'm having trouble with adding the plotly graphs as a html string to the geojson popups. Could someone help me with this? I'll add a code snippet of the folium map and what I've tried:

import folium
import geopandas as gpd
import codecs

map = folium.Map(location=['51.096246199999996', '4.178629103169916'], tiles="cartodbpositron", zoom_start=9)
geojson_file_df = gpd.read_file('Refgem_geojson.json')

loc = 'Project GEO ICT'
title_html = '''
             <h3 align="center" style="font-size:20px"><b>{}</b></h3>
             '''.format(loc)
map.get_root().html.add_child(folium.Element(title_html))


g_map = folium.GeoJson(
    geojson_file,
    name="GeoJson",
    style_function=lambda x: {'fillColor': 'orange'}
).add_to(map)


folium.GeoJsonTooltip(
    fields=['NISCODE','NAAM', 'OPPERVL'],
    aliases=['NISCODE', 'Naam', 'Oppervlakte'],
    sticky=False
).add_to(g_map)


folium.GeoJsonPopup(
    fields=["NAAM", "Average Prices: " ,"Woonhuis", "Villa", "Studio"],
    aliases=["Naam", "Average Prices: ","Woonhuis", "Villa", "Studio"]
).add_to(g_map)

html="""
    <iframe src=\"""" + codecs.open("graphs/AARTSELAAR.html", 'r').read() + """\" width="850" height="400"  frameborder="0">    
    """
popup1 = folium.Popup(folium.Html(html, script=True))

folium.Marker(['51.096246199999996','4.178629103169916'],popup=popup1,icon=folium.Icon( icon='home', prefix='fa')).add_to(map)

map

Here ^ I tried to add the popup to a marker, but that didn't work for me (it's also not really what I want, I want to add the popup to a polygon). I believe I should make some sort of loop that iterates over all features in the geoJson and adds a popup for every iteration.


Solution

  • You have not provided sample data / geometry so used standard geopandas sample data

    • this will create popups / tooltips for each geometry. The popup is a plotly figure convented to an embedded URI encoded image. A pie chart of population of country as %age of population of all geometries.
    • investigated customising GeoJsonPopup() but found no solution
    • hence create a layer for each feature with it's own popup
    import geopandas as gpd
    import folium
    from statistics import mean
    import plotly.express as px
    import base64, io
    
    # some geometry
    gdf = gpd.read_file(gpd.datasets.get_path("naturalearth_lowres")).loc[
        lambda d: d["continent"].eq("Europe") & ~d.bounds.lt(-30).any(axis=1)
    ]
    
    # create the map, nicely centered and zoomed
    bounds = gdf.total_bounds
    x = mean([bounds[0], bounds[2]])
    y = mean([bounds[1], bounds[3]])
    location = (y, x)
    
    m = folium.Map(location=location)
    m.fit_bounds([[bounds[1], bounds[0]], [bounds[3], bounds[2]]])
    
    # given need to create a geojson layer for each figure,  create
    # feature group to contain them
    fg = folium.FeatureGroup(name="Europe", show=False)
    
    # create an encocded image of graph...
    # change to generate graph you want
    def b64image(vals=[1, 2]):
        fig = (
            px.pie(values=vals)
            .update_layout(margin={"l": 0, "r": 0, "t": 0, "b": 0})
            .update_traces(texttemplate="%{percent:.0%}")
        )
    
        b = io.BytesIO(fig.to_image(format="png", width=80, height=80))
        b64 = base64.b64encode(b.getvalue())
        return "data:image/png;base64," + b64.decode("utf-8")
    
    
    tot_pop = gdf["pop_est"].sum()
    
    # create a geojson layer for each feature
    for i, r in gdf.iterrows():
        # geodataframe of row
        gdf_ = gpd.GeoDataFrame(r.to_frame().T, crs=gdf.crs)
        # URI encoded image of plotly figure
        img_ = f'<img src="{b64image([r["pop_est"], tot_pop-r["pop_est"]])}"/>'
    
        choro_ = folium.GeoJson(
            gdf_.__geo_interface__,
            name=r["name"],
            style_function=lambda x: {"fillColor": "orange"},
            tooltip=folium.GeoJsonTooltip(gdf_.drop(columns="geometry").columns.tolist()),
        )
        # this is the real work around, add to layer which is a choro
        folium.Popup(img_).add_to(choro_)
        choro_.add_to(fg)
    
    fg.add_to(m)
    
    m
    

    enter image description here