🗺️

Pyppeteerを使ってfoliumの地図を画像に保存する最小限の構成を試す

2022/08/22に公開

はじめに

以前、Pythonの地図可視化ライブラリとしてよく利用されているFoliumと、ルート検索APIであるOSRM (Open Source Routing Machine)の連携について紹介しました。

https://zenn.dev/takilog/articles/2be029ccd35972

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に説明されているスクリーンショットの例をそのまま利用します。

https://github.com/pyppeteer/pyppeteer

例の使い方(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")

こうして出力したマップがこちらになります。

githubのレポジトリへ直リンクさせた地図の画像ファイル

無事に画像ファイルを保存することができました。

まとめ

上にも書きましたが、より良い手法やライブラリがあることをご存知の方は、ぜひコメントで教えていただければと思います。今回の記事で利用したコードはこちらに置きました。

https://github.com/cocomoff/FoliumRouteSave

Discussion