python-visualization / folium

Python Data. Leaflet.js Maps.
https://python-visualization.github.io/folium/
MIT License
6.92k stars 2.23k forks source link

show folium outside of ipython notebooks #946

Closed Demetrio92 closed 6 years ago

Demetrio92 commented 6 years ago

Hey guys i am trying to run one of the examples you provide. However, I am unable to render it. It's probably because I am using it in a normal python script inside the PyCharm. Is there any functionality similar to matplotlib show()? I tried render() but it does not do anything...

import folium
from folium.plugins import HeatMap
import numpy as np
print(folium.__version__)
#0.6.0

data = (np.random.normal(size=(100, 3)) *
        np.array([[1, 1, 1]]) +
        np.array([[48, 5, 1]])).tolist()
m = folium.Map([48., 5.], tiles='stamentoner', zoom_start=6)
HeatMap(data).add_to(m)
m
# <folium.folium.Map object at 0x7f4c56e14b70>
m.render()
# nothing happens
Demetrio92 commented 6 years ago

So, the answer is no -- there is no such functionality in the package at the moment. Which is unfortunate, as it is easy to implement and e.g. pyLDAvis already solved the problem. I'll provide a custom solution below. For cross-reference, this issue is related to #781 and #455.

Demetrio92 commented 6 years ago

Proof of concept. Tested with python 3.5 and folium 0.6.0

import numpy as np
import folium
from folium.plugins import HeatMap

# create map
data = (np.random.normal(size=(100, 3)) *
        np.array([[1, 1, 1]]) +
        np.array([[48, 5, 1]])).tolist()
folium_map = folium.Map([48., 5.], tiles='stamentoner', zoom_start=6)
HeatMap(data).add_to(folium_map)

# this won't work:
folium_map
folium_map.render()

# ------------------------------------------------------------------------------------------------
# so let's write a custom temporary-HTML renderer
# pretty much copy-paste of this answer: https://stackoverflow.com/a/38945907/3494126
import subprocess
import webbrowser
from http.server import BaseHTTPRequestHandler, HTTPServer

PORT = 7000
HOST = '127.0.0.1'
SERVER_ADDRESS = '{host}:{port}'.format(host=HOST, port=PORT)
FULL_SERVER_ADDRESS = 'http://' + SERVER_ADDRESS

def TemproraryHttpServer(page_content_type, raw_data):
    """
    A simpe, temprorary http web server on the pure Python 3.
    It has features for processing pages with a XML or HTML content.
    """

    class HTTPServerRequestHandler(BaseHTTPRequestHandler):
        """
        An handler of request for the server, hosting XML-pages.
        """

        def do_GET(self):
            """Handle GET requests"""

            # response from page
            self.send_response(200)

            # set up headers for pages
            content_type = 'text/{0}'.format(page_content_type)
            self.send_header('Content-type', content_type)
            self.end_headers()

            # writing data on a page
            self.wfile.write(bytes(raw_data, encoding='utf'))

            return

    if page_content_type not in ['html', 'xml']:
        raise ValueError('This server can serve only HTML or XML pages.')

    page_content_type = page_content_type

    # kill a process, hosted on a localhost:PORT
    subprocess.call(['fuser', '-k', '{0}/tcp'.format(PORT)])

    # Started creating a temprorary http server.
    httpd = HTTPServer((HOST, PORT), HTTPServerRequestHandler)

    # run a temprorary http server
    httpd.serve_forever()

def run_html_server(html_data=None):

    if html_data is None:
        html_data = """
        <!DOCTYPE html>
        <html>
        <head>
        <title>Page Title</title>
        </head>
        <body>
        <h1>This is a Heading</h1>
        <p>This is a paragraph.</p>
        </body>
        </html>
        """

    # open in a browser URL and see a result
    webbrowser.open(FULL_SERVER_ADDRESS)

    # run server
    TemproraryHttpServer('html', html_data)

# ------------------------------------------------------------------------------------------------

# now let's save the visualization into the temp file and render it
from tempfile import NamedTemporaryFile
tmp = NamedTemporaryFile()
folium_map.save(tmp.name)
with open(tmp.name) as f:
    folium_map_html = f.read()

run_html_server(folium_map_html)
Demetrio92 commented 6 years ago

In the above I probably should've used folium_map._repr_html_() Also there is this SO answer that does it using IPython functionality (e.g. you can also use it vanilla python by simply installing the package) https://stackoverflow.com/a/38797877/3494126

Demetrio92 commented 6 years ago

At some point I might tidy it up, and submit as PR.

ocefpaf commented 6 years ago

If you fancy sending a PR please do but folium can does have an HTML repr and all IDEs that can display that will work. Providing a shortcut for saving and loading a in a browser is not really a priority b/c the use case is very low.

Demetrio92 commented 6 years ago

@ocefpaf can you help me with pycharm + ipython? I am not quite sure how to make it work. I am not used to develop in a Jupiter environment, and I am sure a lot of people feel the same.

ocefpaf commented 6 years ago

pycharm + ipython?

As far as I know IPython does not have an HTML repr, so you will have to save the HTML and load it in a browser. Just like any other python module that produces HTML and must be served to be visualized.

I don't know about pycharm, never used it. Maybe you can ask them about it.

BTW, you can always start a local server with:

python -m http.server 8080

save the file and open it with your favorite browser.

Demetrio92 commented 6 years ago

@ocefpaf so that basically means outside of ipython notebook or jupyter you cannot really render the output without additional steps. Which is quite impractical if you have to do a lot of data manipulation before visualizing the result. It's unfair of you to say "it's on IDE to render HTML".

Yes, I am aware that python can run a web server, which is also what my solution above does. I.e. exactly this:

save the file and open it with your favorite browser.

would be nice to have it as a method to folium.folium.Map.

For now, let's hope everyone who faces the same challenge, will stumble across this thread.

Conengmo commented 6 years ago

If you want to open your map in your web browser you could do something like this:

import os
import webbrowser
filepath = 'C:/whatever/map.html'
m = folium.Map()
m.save(filepath)
webbrowser.open('file://' + filepath)

On my Windows machine this opens the map in my default webbrowser. No need to run a server or anything.

Does this work for you?

ocefpaf commented 6 years ago

@Conengmo note that some of the features, to work properly, must be served.

@Demetrio92 it is no different from a flask app and I don't think it is unfair to say that about the IDE b/c folium ultimately goal is to create a HTML map that must be served. What you ask is outside of the goa of the library.

Demetrio92 commented 6 years ago

@ocefpaf

is outside of the goa of the library

I agree, so there must be a more unified solution than 100 lines of custom code that have to be maintained by someone. If you know if there is a two-liner to serve a webpage from a temp-file and open it in browser point me out to it. I still think it should be a part of the package, for the same reason that map has a save method which could also be "not our problem" (python users should know how to save strings to files). I any case, as far as I understood, you'd accept a PR, so it's on me now.

ocefpaf commented 6 years ago

I still think it should be a part of the package,

I said above that it is not a priority, not that it should not be in folium. Also, PRs welcomed :wink:

Conengmo commented 6 years ago

note that some of the features, to work properly, must be served.

I'm curious, what would those be? It was my understanding that everything is embedded in the single HTML file. I haven't encountered any problems so far, so it would be interesting to learn in what cases a server is needed.

Demetrio92 commented 6 years ago

^ +1 just opening an html file would definitely reduce the coding needed. It's 2 lines. One for a temp file, one for opening it.

ocefpaf commented 6 years ago

It was my understanding that everything is embedded in the single HTML file.

I think there are still a few places where things are not embedded, my memory could be failing me though. Still, embedding was a workaround and I do want to provide a more elegant way in the long run, maybe a global embed=True/False to create cleaner and saner HTMLs.

Conengmo commented 6 years ago

Embedding was a workaround? :) I did not know that. I actually like it, each file is stand-alone and portable. Makes them easy to use and share.

ocefpaf commented 6 years ago

I actually like it, each file is stand-alone and portable. Makes them easy to use and share.

Portability was one of the goals, but the main goal was to get the repr to display correctly on notebooks.

Having the full path to files and other resources yields a cleaner (and readable) HTML, avoid some browsers display limitations (as we can see on some notebooks with many objects and sometimes on chrome/chromium), and provide a better HTML for those who are using folium in production to serve maps as a service.

thioaana commented 3 years ago

you can save the map as html (m.save("myMap.html")) and then open with webbrowser.open("myMap.html"). It works fine with pycharm too

eabase commented 2 years ago

Too bad that #953 was never implemented. It would have made much more sense to have a show_in_browser() method, that would handle temporary files and removing them instead of spamming your working script directory (and possibly overwriting other HTML files) with whatever need to be temporarily created just to see the plot.

The suggestion would be:

Conengmo commented 2 years ago

From folium 0.14.0 onwards Map will have a show_in_browser() method.