【Python】#2 後編 foliumで線をプロット -バス走行ルート地図のカスタマイズ-

Python

はじめに

ご覧いただきありがとうございます。バスの走行ルートを地図にプロットするシリーズの後編です。前編では複数の線を地図上にプロットするところまでを実装してみました。出力された地図ではまだまだ見づらい部分もあるため、後編ではマーカーの色や形といった見た目のカスタマイズをしていきます。

前編を見ていないよ~という方は以下の記事を見てみてください!

カスタマイズのポイント

では、今回追加となる部分の仕様とポイントを確認していきましょう。

 

仕様

  • 各ルート名の凡例表示
  • 線を視認性の高い色へ変更
  • 初期表示位置の変更
  • ポップアップ表示の整形
  • バス停をアイコンから円形マーカーに変更
色変更やマーカーの変更は視認性の向上につながり、ルートの情報を加えることでより分かりやすい地図を目指します!

使用データ

データは前回同様に三重県菰野町のオープンデータライブラリから「コミュニティバスの時刻表」をダウンロードしています。
ダウンロード→コミュニティバスの時刻表

今回使用するデータは2025年4月現在のものです。更新情報はダウンロード先サイトをご確認ください。

 【実践】地図の表示をカスタマイズする

カスタマイズをする際に、データの追加が必要な部分見た目の変更のみで実現できる部分の2つに分けてみいていきましょう!

1. データの追加

各ルート名を表示するために元データを追加します。routes.txtファイルを使って実装を進めていきましょう!

routes.txt → 各ルートの情報(route_short_name)
ルート名を表示する

ルート名を表示するには、routes.txtを読み込んでルート情報をtripsに結合していきます。(ファイル読み込みの解説は省略します)

# tripsにルート名を結合
    trips = trips.merge(routes, on="route_id", how="left")

あとはroute_idの数分繰り返し処理をしている中でルート名に使う値を宣言して、polyLineで線描画しているときのtooltip=f”{route_name}”に変更すれば完了です!

route_name = trip.get('route_short_name', '').strip()

これで情報を追加することができました。今までroute_idが表示されていた部分がきちんと路線名になっています。

ルートの情報はこの後の見た目のカスタマイズにも使っていきます。

2. 地図の見た目変更

地図を使いやすくするために見た目を整えていきます。実装したい4点、視認性の高い線の色への変更、初期表示位置の変更、マーカー&ポップアップ表示の整形、凡例追加をそれぞれ確認していきます!

線の色変更

線の色はcolorsの配列を変更すればOKです。色の名前で指定していますが16進数カラーコード(例 red→#ff0000)でも指定可能なので好きな色に変えてみても良いですね。

# 色リスト
    colors = ["red", "navy", "orange" ,"green", "blue", "purple",
    "deeppink", "gold", "magenta", "lime","brown"]
初期表示位置の変更
地図の初期表示の中心が最初のルートの最初のバス停位置になっていたため、全体の中心からずれていました。そこで、地図の中心を全バス停の緯度・経度の中央値に設定します。ズーム率はお好みで変更してください。
    # 地図の中心を全バス停の緯度経度の中央値
    lat_center = stops["stop_lat"].mean()
    lon_center = stops["stop_lon"].mean()
    m = folium.Map(location=[lat_center, lon_center], zoom_start=12)
マーカー&ポップアップ表示の整形

マーカー&ポップアップ表示の設定は簡単に行えます。

# バス停にマーカーを追加する(円・ポップアップ設定)
        if show_markers:
            for _, row in merged.iterrows():
                popup_html = f"<p>{row['stop_name']}</>"
                folium.CircleMarker(
                    location=[row['stop_lat'], row['stop_lon']],
                    radius=5,
                    color='black',       # 枠線の色
                    weight=1.5,          # 枠線の太さ
                    fill=True,
                    fill_color=color,
                    fill_opacity=0.8,
                    popup=folium.Popup(popup_html, max_width=250)
                ).add_to(m)

まず、今までインフォメーションアイコンを表示していた部分を見やすくするために円マーカーにします。 folium. Marker() 関数を使っていた部分をfolium. CircleMarker() 関数に変更して円の大きさ、枠線の色、太さ、塗りつぶし、塗りつぶしの色、透明度を設定しています。
ポップアップ表示popupをHTML形式にして余計な改行をなくしています。

凡例追加

最後にルート・路線名と色を把握できるように凡例を追加しましょう。foliumには標準で凡例を追加する機能はないため、HTMLを記述して地図に埋め込む方法で実装しています。MacroElement を使ってHTML の凡例を地図に追加するため最初にインポートしておきます。

from branca.element import Template, MacroElement

次に凡例の記述部分です。

    legend_items = []


    for i, route_id in enumerate(unique_routes):
        # 省略
        # 線の描画
        points = list(zip(merged["stop_lat"], merged["stop_lon"]))
        folium.PolyLine(
            # 省略
        ).add_to(m)

        # バス停にマーカーを追加する(円・ポップアップ設定)
        if show_markers:
            for _, row in merged.iterrows():
                popup_html = f"<p>{row['stop_name']}</>"
                folium.CircleMarker(
                    # 省略
                ).add_to(m)

        # 凡例用データ追加
        legend_items.append(f"<li style='color:{color}; padding:8px 0;'>{route_name}</li>")


    # 凡例HTML
    legend_html = f"""
    <div style="
        position: fixed;
        top: 20px;
        right: 20px;
        width: 280px;
        background: white;
        font-weight: bold;
        border:2px solid gray;
        border-radius:6px;
        z-index:9999;
        font-size:12px;
        box-shadow: 2px 2px 6px rgba(0,0,0,0.3);
    ">
    <div style="padding:6px;">
        <p>コース・路線名</p>
        <ul style="list-style:none;padding:0;margin:0;">
            {''.join(legend_items)}
        </ul>
    </div>
    </div>
    """


    # 凡例追加
    legend = MacroElement()
    legend._template = Template(f"""{{% macro html(this, kwargs) %}}{legend_html}{{% endmacro %}}""")
    m.get_root().add_child(legend)

ループで色と名前を対応づけしながら<li>要素を追加し、作ったHTMLを最後に地図に埋め込んでいます。

これで全てのカスタマイズも完了です!!出力された地図はこんな感じです。

 全体ソースコード

これまで前編と後編で実装してきた全体のソースコードを記載しておくので見返したい部分はぜひ確認してみてください!

import pandas as pd
import folium
from branca.element import Template, MacroElement


def draw_route(
    stops_file, stop_times_file, trips_file, routes_file,
    output_html="all_routes_map.html",
    show_markers=True
):
    # データ読み込み
    stops = pd.read_csv(stops_file)
    stop_times = pd.read_csv(stop_times_file)
    trips = pd.read_csv(trips_file)
    routes = pd.read_csv(routes_file)


    # tripsにルート名を結合
    trips = trips.merge(routes, on="route_id", how="left")


    # 地図の中心を全バス停の緯度経度の中央値
    lat_center = stops["stop_lat"].mean()
    lon_center = stops["stop_lon"].mean()
    m = folium.Map(location=[lat_center, lon_center], zoom_start=12)


    # 色リスト
    colors = [
    "red", "navy", "orange" ,"green", "blue", "purple",
    "deeppink", "gold", "magenta", "lime","brown"]


    unique_routes = trips["route_id"].unique()
    print(f"route_id数: {len(unique_routes)}件")


    legend_items = []


    for i, route_id in enumerate(unique_routes):
        route_trips = trips[trips["route_id"] == route_id]
        trip = route_trips.iloc[0]  # 最初のtripを使う
        trip_id = trip["trip_id"]
        route_name = trip.get('route_short_name', '').strip()
        color = colors[i % len(colors)]


        # バス停取得&結合
        trip_stops = stop_times[stop_times["trip_id"] == trip_id].sort_values("stop_sequence")
        merged = trip_stops.merge(stops, on="stop_id")


        # 線の描画
        points = list(zip(merged["stop_lat"], merged["stop_lon"]))
        folium.PolyLine(
            points,
            color=color,
            weight= 4.5 ,
            opacity= 1 ,
            tooltip=f"{route_name}"
        ).add_to(m)


        # バス停にマーカーを追加する(円・ポップアップ設定)
        if show_markers:
            for _, row in merged.iterrows():
                popup_html = f"<p>{row['stop_name']}</>"
                folium.CircleMarker(
                    location=[row['stop_lat'], row['stop_lon']],
                    radius=5,
                    color='black',       # 枠線の色
                    weight=1.5,          # 枠線の太さ
                    fill=True,
                    fill_color=color,
                    fill_opacity=0.8,
                    popup=folium.Popup(popup_html, max_width=250)
                ).add_to(m)


        # 凡例用データ追加
        legend_items.append(f"<li style='color:{color}; padding:8px 0;'>{route_name}</li>")


    # 凡例HTML
    legend_html = f"""
    <div style="
        position: fixed;
        top: 20px;
        right: 20px;
        width: 280px;
        background: white;
        font-weight: bold;
        border:2px solid gray;
        border-radius:6px;
        z-index:9999;
        font-size:12px;
        box-shadow: 2px 2px 6px rgba(0,0,0,0.3);
    ">
    <div style="padding:6px;">
        <p>コース・路線名</p>
        <ul style="list-style:none;padding:0;margin:0;">
            {''.join(legend_items)}
        </ul>
    </div>
    </div>
    """


    # 凡例追加
    legend = MacroElement()
    legend._template = Template(f"""{{% macro html(this, kwargs) %}}{legend_html}{{% endmacro %}}""")
    m.get_root().add_child(legend)


    # 地図保存
    m.save(output_html)
    print(f"地図を保存しました: {output_html}")


# 使用例(ファイルパスは適宜変更)
draw_route(
    stops_file="stops.txt",
    stop_times_file="stop_times.txt",
    trips_file="trips.txt",
    routes_file="routes.txt"
)

まとめ

foliumで位置情報を使ってバスの走行ルートをプロットしてきましたが、必要なデータがあれば様々な表示方法があるのだと分かりました。全体の中でデータの加工やCSS、HTMLについてポイントになる部分も多かったですね。オープンデータの活用の幅は広いなと感じました!!

自分の住んでいる自治体のデータで同じように路線図を作ってみてもおもしろそうです♪

最後まで読んでいただきありがとうございました。この記事が参考になると嬉しいです。

タイトルとURLをコピーしました