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

access the map's html, without saving the file #781

Closed
Alcampopiano opened this issue Nov 28, 2017 · 15 comments
Closed

access the map's html, without saving the file #781

Alcampopiano opened this issue Nov 28, 2017 · 15 comments

Comments

@Alcampopiano
Copy link

Hi guys,

Hopefully an easy one. I'm making a few changes to the html once the map is saved to a file. Is there a way to access the html directly, without using m.save()?

@Conengmo
Copy link
Member

I was wondering this myself as well. I think I found out how, by following the parent classes from Map to LegacyMap and then MacroElement and Element in Branca. That last one has the save() method you want to imitate.
You can call get_root() on the Map object and get the Figure object that is created in the initialization of Map. On that you can call render() to create the complete html:
m = folium.Map()
html_string = m.get_root().render()

@Alcampopiano
Copy link
Author

Hi Frank. Thanks for digging into this. What I am trying to do is to be able to make changes to the map's HTML, but still display it so that it will show up in the notebook when it's exported as HTML.

When I use iPython like so,
display(HTML('<iframe src=' + myMap.html + ' width=100% height=1000></iframe>'))

The resulting map is not fully embedded/portable in the HTML notebook (it can't be sent to colleagues).

So basically, is there a way to be able to make changes to the map's HTML, but still have it fully embedded in the notebook when it's exported as HTML?

Sincerely,
Al

@fitoprincipe
Copy link

Hi, I am trying to use Folium in a web application and had a similar issue (I don't use jupyter).
Have you tryied with

m = folium.Map()
iframe = m._repr_html_()

and then, I guess

display(HTML(iframe))

@Alcampopiano
Copy link
Author

@fitoprincipe That will display the map with base64 encoding, which is great, but I am trying to make adjustments ("hacks") to the HTML string first, and then have it get rendered with base64 encoding.

So for example, as @Conengmo suggested, if I wanted to force a heat map to stay on top of a choropleth, I could append some HTML to the main HTML that Folium creates, as follows:

html_data=html_data + '<style>.leaflet-heatmap-layer{z-index:300 !important}</style>'

The question now is how to display this new map, with base64 encoding, so that the map is nice and portable.

I've tried converting my altered HTML to base64, prior to displaying it by using,

import base64
b64 = base64.b64encode(html_data.encode())

But that just shows a blank page.
Any ideas?

@Conengmo
Copy link
Member

Conengmo commented Nov 30, 2017

I tested two ways to do this with this example map:

m = folium.Map(location=[45.372, -121.6972], zoom_start=12, tiles='Stamen Terrain')
folium.Marker(location=[45.3288, -121.6625], popup='Mt. Hood Meadows').add_to(m)
folium.LayerControl(collapsed=False).add_to(m)

A dirty way to do it is to replicate the _render_html_() method in a wrapper class. That way you can do anything with the output of m.get_root().render() before putting it in an iframe with the wrappers _render_html_() method.

class MapWrapper:
    def __init__(self, m):
        self.html = m.get_root().render()
    
    def add_html(self, html_string):
        self.html += html_string

    def _repr_html_(self):
        # Copied from folium.element.Figure
        html = "data:text/html;charset=utf-8;base64," + base64.b64encode(self.html.encode('utf8')).decode('utf8')
        iframe = (
        '<div style="width:100%;">'
        '<div style="position:relative;width:100%;height:0;padding-bottom:60%;">'
        '<iframe src="{html}" style="position:absolute;width:100%;height:100%;left:0;top:0;'
        'border:none !important;" '
        'allowfullscreen webkitallowfullscreen mozallowfullscreen>'
        '</iframe>'
        '</div></div>').format
        return iframe(html=html)
    
mapper = MapWrapper(m)
mapper.add_html(('<style>.leaflet-popup-content{color:#FF0000 !important}'
                 '.leaflet-control{color:#00FF00}</style>'))
mapper

A probably better way is to make a class that inherits from Branca's MacroElement class. You can add this to a map object as usual. Maybe this is something to add to Branca/Folium?

import jinja2
class Styling(branca.element.MacroElement):
    def __init__(self, style):
        super(Styling, self).__init__()
        self._name = 'styling'
        self.style_statements = style
        self._template = jinja2.Template("""
            {% macro header(this, kwargs) %}
                <style>{{ this.style_statements }}</style>
            {% endmacro %}
            """)

Styling(('.leaflet-popup-content-wrapper{color:#FF0000 !important}'
         '.leaflet-control{color:#00FF00}')).add_to(m)
m

@Conengmo
Copy link
Member

I found a much easier way in #370:

style_statement = '<style>.leaflet-control{color:#00FF00}</style>'
m.get_root().html.add_child(folium.Element(style_statement))
m

@fitoprincipe
Copy link

I am following along, shouldn't style go in head tag? like

style_statement = '<style>.leaflet-control{color:#00FF00}</style>'
m.get_root().header.add_child(folium.Element(style_statement))
m

@Alcampopiano
Copy link
Author

@Conengmo Frank this is fantastic!

The "dirty" way you described allows me to do everything I need. Specifically I am hacking two things:

  1. Force a heat map to be on top of a choropleth via:
    <style>.leaflet-heatmap-layer{z-index:300 !important}</style>

  2. Force the layer control checkboxes for my heatmaps to be unchecked by default. To do this I use some regular expression to remove the ".add_to(map)" that occurs when the heatmap is created. This keeps the checkbox unchecked when the map is loaded:

html_string = re.sub(r"gradient.*\s*}\).*\s*.*;", 'gradient: {"0.1": "white", "0.5": "black", "1": "white"}})', html_string)

I realise that this is heavy handed, but it works. Importantly, when exporting the notebook as HTML, the maps still need to be working when I send the file to someone else. Thanks to your help, this seems to be working consistently in Firefox with maps that are ~3MB in size. In Chrome, the maps only show up if they are very basic (like the test example above). I'm sure there is an explanation for that though, which I will investigate.

I am using Folium maps to analyze how census data relates to specific schools. I think that is important work, and you have helped to make it possible.

Thanks again,
Allan

@Conengmo
Copy link
Member

Conengmo commented Dec 1, 2017

Glad to help Allen. By the way, your second point is something I encountered as well. I made a PR for it (#772) so maybe we'll see it in a next version.

@fitoprincipe you're right, it should be in header. Thanks for the learning.

@ocefpaf
Copy link
Member

ocefpaf commented Dec 6, 2017

I found a much easier way in #370:

style_statement = '<style>.leaflet-control{color:#00FF00}</style>'
m.get_root().html.add_child(folium.Element(style_statement))
m

Maybe we should document that somewhere...

@nomadcreator
Copy link

Maybe I am asking a quite preliminary question, but how can I show this on a website, such as Django?
"m" won't show anything when I do.

@Conengmo
Copy link
Member

You mean the entire map? If you render the Figure object above your Map object you get the full html page. Like this:

m = folium.Map()
html_string = m.get_root().render()

I once made Flask app that just returns the html, I believe it was something like this.

flask.render_template_string(html_string)

Alternatively, you can try the _repr_html_() method that is used to display maps in Jupyter Notebooks. It outputs html that contains the map in an iframe, so you'd have to include it in your own template. I haven't tried this before.

m = folium.Map()
html_string = m._repr_html_()

@nomadcreator
Copy link

Thank you. Will try Jupyter Notebooks one.

@GrazingScientist
Copy link

m = folium.Map()
html_string = m.get_root().render()

This did the trick for me, returning the HTML as string. Awesome!

@lathkar
Copy link

lathkar commented Aug 22, 2023

I have exported the Folium map to html. Now I want to update the map - like adding another featuregroup or markers and save the changes to the same html file. Requesting the members to suggest how to achieve this. Thanks.

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

No branches or pull requests

7 participants