Python ipyleaflet export map as PNG or JPG or SVG
Asked Answered
F

4

10

I have tried to export a visualisation of data with ipyleaflet as PNG or any other file format but i could not find a method that is working. For example in folium there is map.save(path). Is there a library or method in ipyleaflet that i have missed in my research which helps me to accomplish my goal?

here is some example code to generate a map

from ipyleaflet import *
center = [34.6252978589571, -77.34580993652344]
zoom = 10
m = Map(default_tiles=TileLayer(opacity=1.0), center=center, zoom=zoom)
m

I'd like to export this map as an image file without taking a screenshot manually.

I found two sources that allow to export javascript leaflet maps: https://github.com/aratcliffe/Leaflet.print and https://github.com/mapbox/leaflet-image

Unfortunately i was not able to make use of them in python.

Fumigant answered 28/6, 2017 at 10:50 Comment(0)
F
10

My colleague and I found a decent work around for ipyleaflet (python) image export. Here is how it works. The folium library is required for an export. The GeoJson data in this example is already prepared with style properties:

import folium
map = folium.Map([51., 12.], zoom_start=6,control_scale=True)
folium.GeoJson(data).add_to(map)
map.save('map.html')

This is how the result looks: output

The html file can be further processed in python (windows) with subprocess calls to make a PDF or PNG out of it. I hope this helps as the ipyleaflet doc for python is almost non existant.

Fumigant answered 28/6, 2017 at 13:57 Comment(1)
just curious, what is that data ? DankeHemidemisemiquaver
W
6

For generating html, you can use ipywidgets

from ipywidgets.embed import embed_minimal_html
embed_minimal_html('map.html', views=[m])

If you want to make a PNG, you can use ipywebrtc, more specifically:

Or in code:

from ipywebrtc import WidgetStream, ImageRecorder
widget_stream = WidgetStream(widget=m, max_fps=1)
image_recorder = ImageRecorder(stream=widget_stream)
display(image_recorder)

Saving the PNG:

with open('map.png', 'wb') as f:
    f.write(image_recorder.image.value)

Or converting to pillow image for preprocessing:

import PIL.Image
import io
im = PIL.Image.open(io.BytesIO(image_recorder.image.value))
Weitzman answered 1/5, 2020 at 7:39 Comment(1)
I tried to use this but get an empty map.png fileRobledo
R
0

ipyleaflet supports saving as html. Export of svg and png does not seem to be supported.

https://ipyleaflet.readthedocs.io/en/latest/map_and_basemaps/map.html#save-to-html

m.save('output.html')

I created an issue ticket for ipyleaflet:

https://github.com/jupyter-widgets/ipyleaflet/issues/1083

Robledo answered 3/1, 2023 at 14:52 Comment(0)
D
0

I use this function to convert a map to a PIL image (and then save it in PNG, JPEG, etc.)

import ipyleaflet
from PIL import Image
import requests
import math

# Save a map view in a PIL image
def toImage(m):

    # Bounds and zoom of the current view
    (latmin,lonmin),(latmax,lonmax) = m.bounds
    zoom = m.zoom

    # URLs of all the Tilelayer on the map
    baseUrls = [x.url for x in m.layers if type(x) == ipyleaflet.leaflet.TileLayer]

    # Opacities
    opacities = [x.opacity for x in m.layers if type(x) == ipyleaflet.leaflet.TileLayer]

    # Convert lat/lon/zoom to xtile,ytile.
    # See https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames
    def latlon2tile(lat_deg, lon_deg, zoom):
        lat_rad = (lat_deg * math.pi) / 180.0
        n = math.pow(2,zoom)
        xtile = n * ((lon_deg + 180.0) / 360.0)
        ytile = n * (1 - (math.log(math.tan(lat_rad) + 1.0/math.cos(lat_rad)) / math.pi)) / 2
        return xtile, ytile

    xtile1f,ytile2f = latlon2tile(latmin,lonmin, zoom)
    xtile2f,ytile1f = latlon2tile(latmax,lonmax, zoom)

    xtile1 = int(xtile1f)
    xtile2 = int(xtile2f)
    ytile1 = int(ytile1f)
    ytile2 = int(ytile2f)

    # Amount of pixels to crop on each side
    dx1 = 256*(xtile1f-xtile1)
    dx2 = 256*(xtile2+1-xtile2f)
    dy1 = 256*(ytile1f-ytile1)
    dy2 = 256*(ytile2+1-ytile2f)

    dx1 = round(dx1*100)//100
    dx2 = round(dx2*100)//100
    dy1 = round(dy1*100)//100
    dy2 = round(dy2*100)//100

    # Number of tiles
    nx = xtile2 - xtile1 + 1
    ny = ytile2 - ytile1 + 1

    # Dimension of the overall image
    w = 256 * nx
    h = 256 * ny
    imageTotal = Image.new(mode="RGBA", size=(w,h))

    # Substitute x,y,z into a TileService URL
    def url(baseurl, x,y,zoom):
        return baseurl.replace('{x}',str(int(x))).replace('{y}',str(int(y))).replace('{z}',str(int(zoom)))

    # Cycle on all tiles and compose the overall image
    for x in range(nx):
        xt = xtile1 + x
        xpos = x*256
        for y in range(ny):
            yt = ytile1 + y
            ypos = y*256
            for baseurl,opacity in zip(baseUrls,opacities):
                try:
                    image = Image.open(requests.get(url(baseurl,xt,yt,zoom), stream=True).raw)
                    image = image.convert('RGBA')

                    if opacity < 1.0:
                        # Split image in 4 channels
                        (r,g,b,a) = image.split()

                        # Change the alpha channel
                        # See https://pillow.readthedocs.io/en/stable/reference/Image.html#PIL.Image.eval
                        a = Image.eval(a, lambda px: opacity*px)

                        # Merge 4 channels
                        image = Image.merge('RGBA',(r,g,b,a))

                    # Transparent paste!!!
                    # See https://mcmap.net/q/50151/-how-to-merge-a-transparent-png-image-with-another-image-using-pil
                    imageTotal.paste(image, (xpos,ypos), mask=image)
                except:
                    pass

    # Crop the image
    area_crop = (dx1, dy1, w-dx2, h-dy2)
    return imageTotal.crop(area_crop)

m = ipyleaflet.Map(center=[47, 12], zoom=5, basemap=ipyleaflet.basemaps.OpenTopoMap)
display(m)

Then in another cell of the notebook:

toImage(m)

The function requests the tiles needed to cover the current map bounds of all the TileLayers of the map and builds an overall image, also managing the opacity of the layers.

Debauchee answered 14/8, 2024 at 14:52 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.