Pyppeteerを使ってfoliumの地図を画像に保存する最小限の構成を試す
はじめに
以前、Pythonの地図可視化ライブラリとしてよく利用されているFoliumと、ルート検索APIであるOSRM (Open Source Routing Machine)の連携について紹介しました。
Foliumは大変よく使われているのですが、地図をHTMLとして出力することが多いため、画像にしたいなという気持ちになります。
画像に出力する方法としては、内部で地図のオブジェクト m
に対して、 m._to_png()
を実行する方法があります( 実装はこちらですね)。要するに、何らかのheadlessブラウザで作成したhtmlにアクセスし、そのスクリーンショットを保存することで画像を生成します(例えばこのコメントも、その例です)。しかしheadlessブラウザの設定が正しくされていないと大体エラーになって面倒です。
今回は poetry
を使って pyppeteer
をインストールしてスクリーンショットを撮るのが比較的楽なのでは?となったので、紹介します。より良い手法やライブラリがあることをご存知の方は、ぜひコメントで教えていただければと思います。
実装
早速実装を見てみます。コード自体は以前のコードを流用しており、pyppeteer
を追加でインストールしています(全ての依存ライブラリについてはこちらにあります)。
OSRMを使って経路の緯度経度列を取得する
こちらは以前の記事とほぼ同じものです。今回はディレクトリ以下の ~/.env
にOSRMのAPIキーを保存したので、それを読み込んでいます。
import os
import folium
from os.path import join, dirname
from dotenv import load_dotenv
import openrouteservice
from openrouteservice import convert
if __name__ == '__main__':
dot_env_path = join(dirname(__name__), ".env")
load_dotenv(dot_env_path, verbose=True)
ORS_KEY = os.environ.get("orskey")
client = openrouteservice.Client(key=ORS_KEY)
p1 = 35.70318768906786, 139.75197158765246
p2 = 35.67431306373605, 139.71574523662844
p1r = tuple(reversed(p1))
p2r = tuple(reversed(p2))
mean_lat = (p1[0] + p2[0]) / 2
mean_long = (p1[1] + p2[1]) / 2
# 経路計算 (Directions V2) の列を取得する
routedict = client.directions((p1r, p2r), profile="foot-walking")
geom = routedict["routes"][0]["geometry"]
decoded = convert.decode_polyline(geom)
coord = [(p[1], p[0]) for p in decoded["coordinates"]]
よって以降では、このcoord
をFoliumの地図上にプロットし、それを画像ファイルとして保存することを目指します。
Pyppeteer
こちらのレポジトリのREADMEに説明されているスクリーンショットの例をそのまま利用します。
例の使い方(async
でデコしてから、asyncio
経由で実行する)を見て、それを真似します。
import asyncio
from pyppeteer import launch
async def main(coord, lat, long):
# 今までの描画と同じ
m = folium.Map(location=(lat, long),
tiles='cartodbpositron', zoom_start=14)
vl = folium.vector_layers.PolyLine(locations=coord, color="tomato",
weight=5, opacity=0.75,
line_cap="round", line_join="round")
vl.add_to(m)
m.save("map.html")
# Pyppeteerの機能で一度HTMLを描画し、pngで保存し、HTMLを削除する
browser = await launch(headless=True)
page = await browser.newPage()
await page.goto(f"file://{os.path.abspath('.')}/map.html")
await page.setViewport({'width': 1080, 'height': 1080})
await page.waitFor(5000)
await page.screenshot({'path': 'map.png'})
os.remove("map.html")
こうして出力したマップがこちらになります。
無事に画像ファイルを保存することができました。
まとめ
上にも書きましたが、より良い手法やライブラリがあることをご存知の方は、ぜひコメントで教えていただければと思います。今回の記事で利用したコードはこちらに置きました。
Discussion