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.
data
? Danke – Hemidemisemiquaver