我尝试使用 ipyleaflet 将数据可视化导出为 PNG 或任何其他文件格式,但我找不到有效的方法。例如,在 folium 中有 map.save(path)。 ipyleaflet 中是否有我在研究中错过的库或方法可以帮助我实现我的目标?
这里是一些生成地图的示例代码
from ipyleaflet import *
center = [34.6252978589571, -77.34580993652344]
zoom = 10
m = Map(default_tiles=TileLayer(opacity=1.0), center=center, zoom=zoom)
m
我想将此地图导出为图像文件,而不需要手动截图。
我找到了两个允许导出 javascript 传单地图的来源: https://github.com/aratcliffe/Leaflet.print 和 https://github.com/mapbox/leaflet-image
不幸的是我无法在Python中使用它们。
我和我的同事找到了一个不错的 ipyleaflet (python) 图像导出解决方案。这是它的工作原理。导出需要 folium 库。本例中的 GeoJson 数据已经准备好样式属性:
import folium
map = folium.Map([51., 12.], zoom_start=6,control_scale=True)
folium.GeoJson(data).add_to(map)
map.save('map.html')
结果如下:
可以通过子进程调用在 python(Windows)中进一步处理 html 文件,以将其生成 PDF 或 PNG。我希望这会有所帮助,因为 python 的 ipyleaflet 文档几乎不存在。
要生成 html,您可以使用 ipywidgets
from ipywidgets.embed import embed_minimal_html
embed_minimal_html('map.html', views=[m])
如果你想制作PNG,你可以使用ipywebrtc,更具体地说:
或者用代码:
from ipywebrtc import WidgetStream, ImageRecorder
widget_stream = WidgetStream(widget=m, max_fps=1)
image_recorder = ImageRecorder(stream=widget_stream)
display(image_recorder)
保存PNG:
with open('map.png', 'wb') as f:
f.write(image_recorder.image.value)
或者转换为枕头图像进行预处理:
import PIL.Image
import io
im = PIL.Image.open(io.BytesIO(image_recorder.image.value))
ipyleaflet 支持另存为 html。好像不支持导出svg和png。
https://ipyleaflet.readthedocs.io/en/latest/map_and_basemaps/map.html#save-to-html
m.save('output.html')
我为 ipyleaflet 创建了问题单:
我使用这个函数将地图转换为PIL图像(然后将其保存为PNG、JPEG等)
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://stackoverflow.com/questions/5324647/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(max_zoom=21, center=[47, 12], zoom=5, scroll_wheel_zoom=True, basemap=ipyleaflet.basemaps.OpenTopoMap)
display(m)
然后在笔记本的另一个单元格中:
toImage(m)
该函数请求覆盖地图所有 TileLayer 的当前地图边界所需的图块并构建整体图像,同时管理图层的不透明度。