Foliumの自作プラグインを作る

はじめまして、AI技術開発部 アルゴリズムグループの田村です。
GO株式会社では、KPIを管理するダッシュボードをはじめ、専門知識がなくても扱えるエラー調査ツールなどをStreamlitに展開し活用しています。
GO TechTalk #29 タクシーアプリ『GO』のログ解析の民主化を促進するStreamlitの活用

今回はStreamlit上で扱うことが多い地図描画ライブラリのFoliumで自作のプラグインを作ってみたので紹介します。

Foliumとは

Foliumは、Pythonで地図を作成・操作するためのライブラリです。
地図上に刺せるマーカーなど標準で様々なプラグインが整備されていますが、時々これらの標準プラグインでは実現できない表現をしたいことがあります。
一方でFoliumは内部的にLeafletというJavaScriptライブラリを利用しており、Leafletは豊富なプラグインやカスタマイズ機能を持ち、ユーザーが自作したプラグインを多く公開しています。
この記事では、多くこれらの公開されているLeafletプラグインをFoliumに移植する試みを紹介します。

標準プラグインの実装を確認する

LeafletプラグインをFoliumに移植する前に、既存のFolium標準プラグインがどのようにLeafletプラグインを扱っているのか確認します。
標準プラグインの1つである「BoatMarker」の実装を確認してみます。(URL)
まずは、Foliumが実際のHTMLおよびJavaScriptコードを生成する際に利用するためのテンプレートを定義します。
テンプレートにはプラグイン(つまりBoatMarker)であらかじめ定義されているマーカーの向きや色などをパラメータを受け取らせるようにしています。

 _template = Template(
        """
        {% macro script(this, kwargs) %}
            var {{ this.get_name() }} = L.boatMarker(
                {{ this.location|tojson }},
                {{ this.options|tojavascript }}
            ).addTo({{ this._parent.get_name() }});
            {% if this.wind_heading is not none -%}
            {{ this.get_name() }}.setHeadingWind(
                {{ this.heading }},
                {{ this.wind_speed }},
                {{ this.wind_heading }}
            );
            {% else -%}
            {{this.get_name()}}.setHeading({{this.heading}});
            {% endif -%}
        {% endmacro %}
        """
    )

次に外部リソースとしてBoatMarkerのJavaScriptCSSのURLを定義します。

default_js = [
    (
    "markerclusterjs",
    "https://unpkg.com/leaflet.boatmarker/leaflet.boatmarker.min.js",
    ),
]

最後に、クラスとしてまとめ与えられたinitの引数をテンプレートに渡してあげる形にしています。

自作プラグインを作る

前述したBoatMarkerの実装を参考にして、Leafletプラグインである「leaflet.marker.highlight 」をFoliumの自作プラグインとして実装してみます。
テンプレートをleaflet.marker.highlightのパラメータを確認しながら書きます。

_template = Template(
    """
    {% macro script(this, kwargs) %}
        var {{ this.get_name() }} = L.marker(
            {{ this.location|tojson }}, // マーカーの緯度経度
            { highlight: {{ this.highlight|tojson }} } // マーカーのハイライト方法
        ).addTo({{ this._parent.get_name() }});
    {% endmacro %}
    """
)

JavaScriptCSSのリソースをjsdelivrで見つけ定義します。(リポジトリにあるファイルを直接手元に落としてきても大丈夫なはず)

default_js = [
    (
    "highlight_marker",
    "https://cdn.jsdelivr.net/npm/leaflet.marker.highlight@0.0.3/index.js",
    )
]
default_css = [
    (
    "leaflet_css",
    "https://cdn.jsdelivr.net/npm/leaflet.marker.highlight@0.0.3/index.css"
    )
]

最後にクラスとしてまとめます。

class HighlightMarker(JSCSSMixin, MacroElement):
    _template = Template(
        """
        {% macro script(this, kwargs) %}
            var {{ this.get_name() }} = L.marker(
                {{ this.location|tojson }},
                { highlight: {{ this.highlight|tojson }} }
            ).addTo({{ this._parent.get_name() }});
        {% endmacro %}
        """
    )

    default_js = [
        (
        "highlight_marker",
        "https://cdn.jsdelivr.net/npm/leaflet.marker.highlight@0.0.3/index.js",
        )
    ]
    default_css = [
        (
        "leaflet_css",
        "https://cdn.jsdelivr.net/npm/leaflet.marker.highlight@0.0.3/index.css"
        )
    ]
    def __init__(self, location, highlight="permanent", **kwargs):
        super().__init__()
        self._name = "HighlightMarker"
        self.location = location
        self.highlight = highlight
        self.options = parse_options(line=True, **kwargs)
        self.options.update(
            {'highlight': self.highlight}
        )

実際に地図上に描画してみます。

m = folium.Map(location=[35.680959106959,139.76730676352], zoom_start=10)
 HighlightMarker(location=[35.65858404079,139.74543164468],highlight="permanent").add_to(m) # 東京タワー
HighlightMarker(location=[35.710006892117,139.81081025188],highlight="temporary").add_to(m) # スカイツリー
m

画像だと伝わりませんが、東京タワーのマーカーは常に青色が点滅し、スカイツリーのマーカーはマウスをホバーすると黄色に点滅します。

おわりに

今回はFoliumでシンプルな自作プラグインの実装例を紹介しました。
前述したようにLeafletには様々なプラグインが公開されており、より複雑なプラグインの移植にもチャレンジできたらと思っています!

参考