Python Folium Relearning(7) from May 2025(Popup and Marker Plotting)

 前回:https://shikuuk.blogspot.com/2025/06/python-folium-relearning6-from-may.html

 次はPopupです。FoliumHomePageはこちら(https://python-visualization.github.io/folium/latest/user_guide/ui_elements/popups.html)です。FoliumのContentsは少しSideにPutして、わたしなりにGemini先生に聞きながら、国土地理情報に格納されている国内のダムに関する情報(https://nlftp.mlit.go.jp/ksj/gml/datalist/KsjTmplt-W01.html)を使って、ダムの位置にMarkerをPlotし、DamのInfomaionをPopupさせてみます。

 まず最初に国土数値情報からダムの情報をGetし、GeoDataFrameにTransformするCodeです。DamInformationのURLはChromeのその他ツールのデベロッパーツールを活用して調べました。

>Code<

import urllib.request
import zipfile
import geopandas as gpd

def download_map_data(url, zip_file_name, shp_file_name):
    zip_file_path = 'unzipped_data/' + zip_file_name

# URLからデータをダウンロード
    urllib.request.urlretrieve(url, zip_file_name)

# ZIPファイルを解凍
    with zipfile.ZipFile(zip_file_name, 'r') as zip_ref:
        zip_ref.extractall('unzipped_data')  # 解凍先のディレクトリを指定

# Shapefileを読み込む
#shapefile_path = 'unzipped_data/W05-06_36-g_Stream.shp'
    shp_file_path = 'unzipped_data/' + shp_file_name
    gdf = gpd.read_file(shp_file_path)

    return gdf

# データフレームを格納するリスト
gdf_shikoku_river = gpd.GeoDataFrame()

# ダウンロードとデータ読み込みのループ
url = 'http://nlftp.mlit.go.jp/ksj/gml/data/W01/W01-14/W01-14_GML.zip' # 国土数値情報のダムデータを格納しているURLをChoromeのデベロッパーツール活用して確認
zip_file_name = 'W01-14_GML.zip'
shp_file_name = 'W01-14-g_Dam.shp'# 国土数値情報のダムデータを一度Downloadしてshpファイルの名称を確認

# データをダウンロードして読み込む
gdf = download_map_data(url, zip_file_name, shp_file_name)

# データを表示
gdf.head()

>Output<

W01_001W01_002W01_003W01_004W01_005W01_006W01_007W01_008W01_009W01_010W01_011W01_012W01_013W01_014geometry
0駒込191堤川駒込川71,2,684.5270.032078004-青森県青森市大字駒込4POINT (140.88589 40.70977)
1下湯192堤川堤川121,2,470.0783.537231260041988青森県青森市大字荒川字横倉1POINT (140.77942 40.69178)
2浅虫193浅虫川浅虫川71,29.0215.01030042002青森県青森市大字浅虫字山下1POINT (140.87188 40.88522)
3田の沢溜池194小湊川小湊川3321.0205.0124117451945青森県東津軽郡平内町大字松野木1POINT (140.99147 40.89848)
4清水目195野辺地川野辺地川7133.5195.075263042000青森県上北郡東北町字清水目1POINT (141.07611 40.81095)

 今回はGemini先生に色々と聞きました。まずはGemini先生への1問目の質問です。

>Gemini先生へのQuestion<

国土数値情報のダム情報のShapeファイルをダウンロードして作成したGeoPandasDataFrame'gdf'があります。これらのダムの位置情報(緯度・経度)をもとにFoliumのOpenStreetMap上でマーカーを表示した上で、tooltipにダム名(ColumnName'W01_001')を、PopUpで、水系名(ColumnName'W01_003')、ダムの形式(ColumnName'W01_005')、ダムの目的(ColumnName'W01_006')、ダムの堤高(m)(ColumnName'W01_007')、ダムの総貯水容量(千m3)(ColumnName'W01_010'),所在地ColumnName'W01_013')を表示したいです。

なお、ダムの形式は以下のようなコードです。

コード対応する内容1アーチダム2バットレスダム3アースダム4アスファルトフェイシングダム5アスファルトコアダム6フローティングゲートダム(可動堰)7重力式コンクリートダム8重力式アーチダム9重力式コンクリートダム・フィルダム複合ダム10中空重力式コンクリートダム11マルティプルアーチダム12ロックフィルダム13台形CSGダム。さらに、ダムの目的コードは以下のようなコードです。

コード対応する内容1洪水調節、農地防災2不特定用水、河川維持用水3灌漑、特定(新規)灌漑用水4上水道用水5工業用水道用水6発電7消流雪用水8レクリエーション。

さらに、ダムの目的別にマーカーの色を変えたいです。

 Actually、ThisCodeでOutputされたMapのPopupでは'ダムの種類’と'ダムの目的'がすべて不明と出力されました。ということで、TeacherGeminiに改善の質問です。

>Gemini先生へのQuestion<

引き続き、Popupでダムの形式と、ダムの目的が全て'不明'で出て来ます

>TeacherGeminiのAnswer<

ポップアップでダムの形式と目的がすべて「不明」と表示されるとのこと、承知いたしました。これは主に以下の2つの原因が考えられます。

  1. Shapeファイルの読み込み時のエンコーディングの問題: 国土数値情報のShapeファイルは、しばしばShift-JISでエンコードされています。もし読み込み時に正しいエンコーディングを指定できていない場合、文字化けが発生し、数値データであるはずのW01_005W01_006カラムが正しく読み込まれず、その結果、辞書によるマッピングが機能せずfillna('不明')が適用されてしまいます。

  2. W01_005W01_006カラムのデータ型が文字列になっている: Shapeファイルの読み込み方によっては、数値として扱われるべきカラムが文字列として読み込まれてしまうことがあります。この場合、辞書のマッピングは数値(整数)キーで行われるため、文字列として読み込まれたカラムの値とは一致せず、「不明」となってしまいます。...

以下、Answerは続くのですが、これまでの経験から2.の可能性が高いと判断して、MasterGeminiのSuggesting Codeを活用して、以下の通りCodeをFixします。

>Gemini先生にOfferいただいたCode< わたくしが少し手を加えてますが、ほぼママです。

import folium
import pandas as pd

# --- 2. ダム形式と目的のコードを対応する内容に変換する辞書 ---
dam_type_map = {
    1: 'アーチダム',
    2: 'バットレスダム',
    3: 'アースダム',
    4: 'アスファルトフェイシングダム',
    5: 'アスファルトコアダム',
    6: 'フローティングゲートダム(可動堰)',
    7: '重力式コンクリートダム',
    8: '重力式アーチダム',
    9: '重力式コンクリートダム・フィルダム複合ダム',
    10: '中空重力式コンクリートダム',
    11: 'マルティプルアーチダム',
    12: 'ロックフィルダム',
    13: '台形CSGダム'
}

dam_purpose_map = {
    1: '洪水調節、農地防災',
    2: '不特定用水、河川維持用水',
    3: '灌漑、特定(新規)灌漑用水',
    4: '上水道用水',
    5: '工業用水道用水',
    6: '発電',
    7: '消流雪用水',
    8: 'レクリエーション'
}

# ダムの目的ごとにマーカーの色を定義する辞書
# 目的の数が多いので、代表的な色を割り当て、足りなければ追加してください。
# また、色の選択は視覚的に分かりやすいように調整してください。
purpose_colors = {
    1: 'blue',     # 洪水調節、農地防災
    2: 'green',    # 不特定用水、河川維持用水
    3: 'orange',   # 灌漑、特定(新規)灌漑用水
    4: 'red',      # 上水道用水
    5: 'purple',   # 工業用水道用水
    6: 'darkgreen', # 発電
    7: 'cadetblue', # 消流雪用水
    8: 'pink',     # レクリエーション
    # その他の目的コードがあれば、ここに色を追加
}

# データ型が数値型でない(例: object/string)場合は変換を試みる
# errors='coerce' は変換できない値をNaNにするオプション
gdf['W01_005'] = pd.to_numeric(gdf['W01_005'], errors='coerce').astype('Int64') # Nullable Integer
gdf['W01_006'] = pd.to_numeric(gdf['W01_006'], errors='coerce').astype('Int64') # Nullable Integer

# --- 3. 変換した内容を新しいカラムとして追加 ---
gdf['ダム形式'] = gdf['W01_005'].map(dam_type_map).fillna('不明')
gdf['ダム目的'] = gdf['W01_006'].map(dam_purpose_map).fillna('不明')

# --- 4. Folium マップの作成 ---
# マップの中心を日本の平均的な緯度経度にするか、データの中央値に設定
if not gdf.empty:
    center_lat = gdf.geometry.y.mean()
    center_lon = gdf.geometry.x.mean()
else:
    center_lat = 35.6895  # 東京の緯度(デフォルト)
    center_lon = 139.6917 # 東京の経度(デフォルト)

m = folium.Map(location=[center_lat, center_lon], zoom_start=6)

# --- 5. 各ダムに対してマーカーを追加 ---
for idx, row in gdf.iterrows():
    # 緯度・経度を取得
    lat = row.geometry.y
    lon = row.geometry.x

    # ツールチップのHTMLを生成
    tooltip_text = row['W01_001'] # ダム名

    # ポップアップのHTMLを生成
    popup_html = f"""
    <h4>{row['W01_001']}</h4>
    <p><b>水系名:</b> {row['W01_003']}</p>
    <p><b>ダムの形式:</b> {row['ダム形式']}</p>
    <p><b>ダムの目的:</b> {row['ダム目的']}</p>
    <p><b>ダムの堤高(m):</b> {row['W01_007']} m</p>
    <p><b>ダムの総貯水容量(千m3):</b> {row['W01_010']} 千m3</p>
    <p><b>所在地:</b> {row['W01_013']}</p>
    """

    # ダムの目的コードに基づいてマーカーの色を取得
    purpose_code = row['W01_006']
    marker_color = purpose_colors.get(purpose_code, 'gray') # コードがない場合は灰色

    # マーカーの追加
    folium.Marker(
        location=[lat, lon],
        tooltip=tooltip_text,
        popup=folium.Popup(popup_html, max_width=300),
        icon=folium.Icon(color=marker_color, icon='info-sign') # icon='info-sign'は例
    ).add_to(m)

# --- 7. レイヤーコントロールを追加 ---
folium.LayerControl().add_to(m)
"""
# --- 6. マップを保存または表示 ---
# HTMLファイルとして保存
map_output_path = 'dam_map.html'
m.save(map_output_path)
print(f"マップが '{map_output_path}' に保存されました。ブラウザで開いて確認してください。")
"""
# Jupyter Notebook/Lab環境であれば、直接表示
m

>Output<

 日本地図がMarkerで塗りつぶされてしまいました。
 ちなみに、gdf.shapeすると、Outputが(2749,15)になったので、2749個のDamがEntryされています。

 琵琶湖周辺をZoomUpしてTooltipやPopupを確認しました。Tooltipとして神戸にある一庫ダムが、Popupとして改造前の天ヶ瀬ダムの諸元が表示されています。
 このCodeに関するGemini先生の解説をRemindMemoとしてCopy&Pasteしておきます。

>Gemini'sTeaching<
ダム形式と目的のコード変換辞書:
    • dam_type_mapdam_purpose_map は、提供されたコードと対応する内容をマッピングするための辞書です。
    • purpose_colors は、ダムの目的コードに対応するマーカーの色を定義するための辞書です。目的に応じて好きな色を設定してください。
  • 変換した内容を新しいカラムとして追加:

    • gdf['W01_005'].map(dam_type_map).fillna('不明') を使って、W01_005(ダム形式コード)を対応する文字列に変換し、新しいカラム 'ダム形式' としてgdfに追加します。fillna('不明') は、マッピングできなかったコードがある場合に「不明」と表示するためのものです。
    • 同様に、W01_006(ダム目的コード)も 'ダム目的' カラムに追加します。
  • Folium マップの作成 (folium.Map):

    • folium.Map(location=[center_lat, center_lon], zoom_start=6) でベースとなる地図を作成します。
    • location には地図の中心となる緯度と経度を指定します。ここではgdfのデータの平均値を使用しています。データがない場合のデフォルト値も設定しています。
    • zoom_start は初期のズームレベルです。
  • 各ダムに対してマーカーを追加 (folium.Marker):

    • gdf.iterrows() を使って、gdfの各行(つまり各ダム)をループ処理します。
    • row.geometry.y で緯度、row.geometry.x で経度を取得します。
    • ツールチップ (tooltip): tooltip_text = row['W01_001'] でダム名をツールチップに設定します。
    • ポップアップ (popup): popup_html にf-stringを使ってHTML形式で情報を記述します。ここに水系名、ダムの形式、目的、堤高、総貯水容量、所在地を表示します。
    • マーカーの色 (icon=folium.Icon(color=marker_color)): purpose_colors.get(purpose_code, 'gray') を使って、ダムの目的コードに対応する色を取得します。もし対応するコードがない場合は 'gray'(灰色)が使われます。folium.Iconcolor 引数にこの色を渡し、マーカーの色を変更します。icon='info-sign' はマーカー内のアイコンの例です。Foliumで利用可能な他のアイコンも試すことができます。
    • 作成したマーカーは add_to(m) で地図に追加します。
  • マップの保存または表示:

    • m.save('dam_map.html') で作成した地図をHTMLファイルとして保存します。このファイルをウェブブラウザで開くことで地図を確認できます。
    • Jupyter NotebookやJupyterLabの環境であれば、最後に m とだけ書くと、セルに出力された地図が直接表示されます。

     TeacherGemini、ここまでPolitelyにExplainしてくれるとはExcellentでございます。

     でも、こんなにMarkerでFillUpされると、HardToSeeなので、MarkerClasterというMethodを使って、EasyToSeeにしたいと思います。ここでもMasterGeminiの登場です。

    >MasterGeminiへのQuestion<

    完璧です。さらに2点改良を加えたいです。

    ①マーカーの数が多すぎてみづらいので、zoomが小さいときはマーカーをまとめて表示するようにできませんか?

    ②'ダムの目的'ごとにマーカーをfeatureGroupに設定して、個別のLayerに格納し、LayerControlで表示・非表示を切り替えできるようにできないでしょうか?

    >MasterGeminiへのAnswer<

    承知いたしました!Foliumで大量のマーカーを効率的に表示し、かつ目的別にレイヤーを切り替えるための2つの重要な改良点ですね。

    1. マーカークラスタリング (Marker Clustering): folium.plugins.MarkerCluster を使用します。これにより、ズームアウト時にはマーカーがグループ化され、ズームインすると個々のマーカーが表示されるようになります。
    2. FeatureGroup と LayerControl: folium.FeatureGroup を使用して目的ごとのダムを個別のレイヤーにまとめ、folium.LayerControl でそれらの表示/非表示を切り替えられるようにします。

    これらの改良を加えたコードを以下に示します。

    import folium
    from folium.plugins import MarkerCluster
    import pandas as pd


    # --- 2. W01_005とW01_006のデータ型を確認し、必要なら変換する ---
    if not gdf.empty:
        print("\n--- カラム 'W01_005' と 'W01_006' の情報 (変換前) ---")
        print(gdf[['W01_005', 'W01_006']].info())
        print("\n--- 'W01_005' の最初の5つの値 (変換前) ---")
        print(gdf['W01_005'].head())
        print("\n--- 'W01_006' の最初の5つの値 (変換前) ---")
        print(gdf['W01_006'].head())

        gdf['W01_005'] = pd.to_numeric(gdf['W01_005'], errors='coerce').astype('Int64')
        gdf['W01_006'] = pd.to_numeric(gdf['W01_006'], errors='coerce').astype('Int64')

        print("\n--- カラム 'W01_005' と 'W01_006' の情報 (変換後) ---")
        print(gdf[['W01_005', 'W01_006']].info())
        print("\n--- 'W01_005' の最初の5つの値 (変換後) ---")
        print(gdf['W01_005'].head())
        print("\n--- 'W01_006' の最初の5つの値 (変換後) ---")
        print(gdf['W01_006'].head())


    # --- 3. ダム形式と目的のコードを対応する内容に変換する辞書とカラーマップ ---
    dam_type_map = {
        1: 'アーチダム', 2: 'バットレスダム', 3: 'アースダム',
        4: 'アスファルトフェイシングダム', 5: 'アスファルトコアダム', 6: 'フローティングゲートダム(可動堰)',
        7: '重力式コンクリートダム', 8: '重力式アーチダム', 9: '重力式コンクリートダム・フィルダム複合ダム',
        10: '中空重力式コンクリートダム', 11: 'マルティプルアーチダム', 12: 'ロックフィルダム',
        13: '台形CSGダム'
    }

    dam_purpose_map = {
        1: '洪水調節、農地防災', 2: '不特定用水、河川維持用水', 3: '灌漑、特定(新規)灌漑用水',
        4: '上水道用水', 5: '工業用水道用水', 6: '発電',
        7: '消流雪用水', 8: 'レクリエーション'
    }

    purpose_colors = {
        1: 'blue',
        2: 'green',
        3: 'orange',
        4: 'red',
        5: 'purple',
        6: 'darkgreen',
        7: 'cadetblue',
        8: 'pink',
        # その他の目的コードがあれば、ここに色を追加(keyを整数型にしてください)
    }

    # 変換した内容を新しいカラムとして追加
    gdf['ダム形式'] = gdf['W01_005'].map(dam_type_map).fillna('不明')
    gdf['ダム目的'] = gdf['W01_006'].map(dam_purpose_map).fillna('不明')

    # --- 4. Folium マップの作成 ---
    if not gdf.empty:
        center_lat = gdf.geometry.y.mean()
        center_lon = gdf.geometry.x.mean()
    else:
        center_lat = 35.6895
        center_lon = 139.6917

    m = folium.Map(location=[center_lat, center_lon], zoom_start=6)

    # --- 5. マーカークラスタリングとFeatureGroupの準備 ---

    # マーカークラスタリング用のグループ (全てのダム用)
    # このグループに個々のマーカーを追加することで、自動的にクラスタリングされる
    marker_cluster = MarkerCluster().add_to(m)

    # ダムの目的ごとにFeatureGroupを作成
    # ダムの目的コードのユニークな値を取得し、ソートしておく
    unique_purposes = sorted(gdf['W01_006'].dropna().unique())

    # FeatureGroupを格納する辞書
    purpose_feature_groups = {}
    for code in unique_purposes:
        purpose_name = dam_purpose_map.get(code, f'目的不明 ({code})')
        # FeatureGroupのレイヤー名は、LayerControlで表示される名前になります
        fg = folium.FeatureGroup(name=f'目的: {purpose_name}').add_to(m)
        purpose_feature_groups[code] = fg

    # NaN(目的コードが不明なもの)も別のFeatureGroupにまとめる
    fg_unknown_purpose = folium.FeatureGroup(name='目的: 不明').add_to(m)
    purpose_feature_groups[pd.NA] = fg_unknown_purpose # pd.NA は NaN も含む

    # --- 6. 各ダムに対してマーカーを追加 ---
    for idx, row in gdf.iterrows():
        lat = row.geometry.y
        lon = row.geometry.x

        tooltip_text = row['W01_001'] # ダム名

        popup_html = f"""
        <h4>{row['W01_001']}</h4>
        <p><b>水系名:</b> {row['W01_003']}</p>
        <p><b>ダムの形式:</b> {row['ダム形式']}</p>
        <p><b>ダムの目的:</b> {row['ダム目的']}</p>
        <p><b>ダムの堤高(m):</b> {row['W01_007']} m</p>
        <p><b>ダムの総貯水容量(千m3):</b> {row['W01_010']} 千m3</p>
        <p><b>所在地:</b> {row['W01_013']}</p>
        """

        purpose_code = row['W01_006']
        marker_color = purpose_colors.get(purpose_code, 'gray') # コードがない場合は灰色

        marker = folium.Marker(
            location=[lat, lon],
            tooltip=tooltip_text,
            popup=folium.Popup(popup_html, max_width=300),
            icon=folium.Icon(color=marker_color, icon='info-sign')
        )

        # ① マーカーをMarkerClusterに追加
        marker.add_to(marker_cluster)

        # ② 目的ごとのFeatureGroupにも追加
        # purpose_codeがNaNの場合は、不明グループに追加
        target_fg = purpose_feature_groups.get(purpose_code, fg_unknown_purpose)
        marker.add_to(target_fg)

    # --- 7. レイヤーコントロールを追加 ---
    folium.LayerControl().add_to(m)
    """
    # --- 8. マップを保存または表示 ---
    map_output_path = 'dam_map_clustered_layered.html'
    m.save(map_output_path)
    print(f"改良されたマップが '{map_output_path}' に保存されました。ブラウザで開いて確認してください。")
    """
    # Jupyter Notebook/Lab環境であれば、直接表示
    m

     Unfortunately、このCodeをDoneすると、全くMarkerがPutされなくなってしましました。再度、MasterGeminiにQuestionです。
    >MasterGeminiへのQuestion<
    Errorは出ませんが、地図上にマーカーが表示されなくなりました

    >MasterGeminiのAnswer<

    マーカーが表示されなくなったとのこと、承知いたしました。エラーが出ないのに表示されない場合、いくつか考えられる原因があります。

    最も一般的なのは、マーカークラスタリングとFeatureGroupの両方に同じマーカーを追加していることで、内部的に競合が発生しているケースです。Foliumでは、マーカーは一つの親グループにのみ属するのが原則です。

    今回は、レイヤーコントロールで表示を切り替えたいので、FeatureGroupにマーカーを追加し、そのFeatureGroup自体をMarkerClusterに追加する、というアプローチを取るのが適切です。

    以下の修正案で、マーカーが表示されるようになるかお試しください。


    修正のポイント

    前回のコードでは、各folium.Markerオブジェクトをmarker_clusterと対応するfolium.FeatureGroup両方add_to()していました。これが問題の原因と考えられます。

    正しいアプローチは次の通りです。

    1. FeatureGroup自体をMarkerClusterのメンバーにする
    2. 個々のマーカーは、それぞれのFeatureGroupにのみ追加する

    これにより、LayerControlでFeatureGroupの表示/非表示を切り替えられ、かつFeatureGroupに含まれるマーカーが自動的にクラスタリングされます。

    >Code<

    import urllib.request
    import zipfile
    import geopandas as gpd

    def download_map_data(url, zip_file_name, shp_file_name):
        zip_file_path = 'unzipped_data/' + zip_file_name

    # URLからデータをダウンロード
        urllib.request.urlretrieve(url, zip_file_name)

    # ZIPファイルを解凍
        with zipfile.ZipFile(zip_file_name, 'r') as zip_ref:
            zip_ref.extractall('unzipped_data')  # 解凍先のディレクトリを指定

    # Shapefileを読み込む
    #shapefile_path = 'unzipped_data/W05-06_36-g_Stream.shp'
        shp_file_path = 'unzipped_data/' + shp_file_name
        gdf = gpd.read_file(shp_file_path)

        return gdf

    # データフレームを格納するリスト
    gdf_shikoku_river = gpd.GeoDataFrame()

    # ダウンロードとデータ読み込みのループ
    url = 'http://nlftp.mlit.go.jp/ksj/gml/data/W01/W01-14/W01-14_GML.zip' # 国土数値情報のダムデータを格納しているURLをChoromeのデベロッパーツール活用して確認
    zip_file_name = 'W01-14_GML.zip'
    shp_file_name = 'W01-14-g_Dam.shp'# 国土数値情報のダムデータを一度Downloadしてshpファイルの名称を確認

    # データをダウンロードして読み込む
    gdf = download_map_data(url, zip_file_name, shp_file_name)

    # データを表示
    gdf.head()


    import geopandas as gpd
    import folium
    from folium.plugins import MarkerCluster
    import pandas as pd
    import numpy as np
    import os

    # --- 2. W01_005とW01_006のデータ型変換と目的リスト化 ---
    if not gdf.empty:
    #    print("\n--- カラム 'W01_005' と 'W01_006' の情報 (変換前) ---")
    #    print(gdf[['W01_005', 'W01_006']].info())
    #    print("\n--- 'W01_005' の最初の5つの値 (変換前) ---")
    #    print(gdf['W01_005'].head())
    #    print("\n--- 'W01_006' の最初の5つの値 (変換前) ---")
    #    print(gdf['W01_006'].head())

        gdf['W01_005'] = pd.to_numeric(gdf['W01_005'], errors='coerce').astype('Int64')

        # W01_006を文字列のリストに変換する関数
        def parse_purposes(purpose_str):
            if pd.isna(purpose_str):
                return []
            try:
                # カンマで分割し、各要素を整数に変換。数値でないものはスキップ。
                return [int(s.strip()) for s in str(purpose_str).split(',') if s.strip().isdigit()]
            except:
                return [] # 変換エラーの場合は空リスト

        # 全ての目的コードのリストを格納するカラム
        gdf['W01_006_list'] = gdf['W01_006'].apply(parse_purposes)

        # マーカーの色分けとFeatureGroup用の単一目的コードを生成
        # 複数目的の場合は '9' (多目的) とする
        def get_single_purpose_code(purpose_list_or_str):
            if isinstance(purpose_list_or_str, list): # 既にリストの場合
                if len(purpose_list_or_str) > 1:
                    return 9 # 複数の場合は多目的
                elif len(purpose_list_or_str) == 1:
                    return purpose_list_or_str[0] # 単一の場合はそのコード
                else:
                    return pd.NA # 空リストの場合はNaN
            else: # まだ文字列などの場合(to_numeric適用前)
                s = str(purpose_list_or_str).strip()
                if not s or s == 'nan':
                    return pd.NA
                parts = s.split(',')
                if len(parts) > 1:
                    return 9 # 複数の場合は多目的
                elif parts[0].isdigit():
                    return int(parts[0]) # 単一の数字の場合
                else:
                    return pd.NA

        gdf['W01_006_for_group'] = gdf['W01_006'].apply(get_single_purpose_code).astype('Int64')

    # --- 3. ダム形式と目的のコードを対応する内容に変換する辞書とカラーマップ ---
    dam_type_map = {
        1: 'アーチダム', 2: 'バットレスダム', 3: 'アースダム',
        4: 'アスファルトフェイシングダム', 5: 'アスファルトコアダム', 6: 'フローティングゲートダム(可動堰)',
        7: '重力式コンクリートダム', 8: '重力式アーチダム', 9: '重力式コンクリートダム・フィルダム複合ダム',
        10: '中空重力式コンクリートダム', 11: 'マルティプルアーチダム', 12: 'ロックフィルダム',
        13: '台形CSGダム'
    }

    dam_purpose_map = {
        1: '洪水調節、農地防災', 2: '不特定用水、河川維持用水', 3: '灌漑、特定(新規)灌漑用水',
        4: '上水道用水', 5: '工業用水道用水', 6: '発電',
        7: '消流雪用水', 8: 'レクリエーション',
        9: '多目的' # 多目的を追加
    }

    purpose_colors = {
        1: 'blue',
        2: 'green',
        3: 'orange',
        4: 'red',
        5: 'purple',
        6: 'darkgreen',
        7: 'cadetblue',
        8: 'pink',
        9: 'darkorange' # 多目的用の色
    }

    # 変換した内容を新しいカラムとして追加
    gdf['ダム形式'] = gdf['W01_005'].map(dam_type_map).fillna('不明')

    # ポップアップ表示用のダム目的文字列を生成
    def format_purposes_for_popup(purpose_codes_list):
        if not purpose_codes_list:
            return '不明'

        purposes = [dam_purpose_map.get(code, f'不明 ({code})') for code in purpose_codes_list]
        return '<br>'.join(purposes) # HTMLの改行タグで結合し、複数の目的を縦に並べる

    gdf['ダム目的_表示用'] = gdf['W01_006_list'].apply(format_purposes_for_popup)


    # --- 4. Folium マップの作成 ---
    if not gdf.empty:
        center_lat = gdf.geometry.y.mean()
        center_lon = gdf.geometry.x.mean()
    else:
        center_lat = 35.6895
        center_lon = 139.6917

    m = folium.Map(location=[center_lat, center_lon], zoom_start=6)

    # --- 5. MarkerClusterとFeatureGroupの準備 ---
    main_marker_cluster = MarkerCluster().add_to(m)

    # FeatureGroupの作成には 'W01_006_for_group' を使用
    unique_purposes = sorted(gdf['W01_006_for_group'].dropna().unique())

    purpose_feature_groups = {}
    for code in unique_purposes:
        purpose_name = dam_purpose_map.get(code, f'目的不明 ({code})')
        fg = folium.FeatureGroup(name=f'目的: {purpose_name}').add_to(main_marker_cluster)
        purpose_feature_groups[code] = fg

    fg_unknown_purpose = folium.FeatureGroup(name='目的: 不明').add_to(main_marker_cluster)
    purpose_feature_groups[pd.NA] = fg_unknown_purpose

    # --- 6. 各ダムに対してマーカーを追加 ---
    for idx, row in gdf.iterrows():
        lat = row.geometry.y
        lon = row.geometry.x

        tooltip_text = row['W01_001']

        # ポップアップで複数の目的を表示するために 'ダム目的_表示用' カラムを使用
        popup_html = f"""
        <h4>{row['W01_001']}</h4>
        <p><b>水系名:</b> {row['W01_003']}</p>
        <p><b>ダムの形式:</b> {row['ダム形式']}</p>
        <p><b>ダムの目的:</b> {row['ダム目的_表示用']}</p>
        <p><b>ダムの堤高(m):</b> {row['W01_007']} m</p>
        <p><b>ダムの総貯水容量(千m3):</b> {row['W01_010']} 千m3</p>
        <p><b>所在地:</b> {row['W01_013']}</p>
        """

        # マーカーの色は FeatureGroup 用の単一目的コードに基づいて設定
        purpose_code_for_color = row['W01_006_for_group']
        marker_color = purpose_colors.get(purpose_code_for_color, 'gray')

        marker = folium.Marker(
            location=[lat, lon],
            tooltip=tooltip_text,
            popup=folium.Popup(popup_html, max_width=300),
            icon=folium.Icon(color=marker_color, icon='info-sign')
        )

        target_fg = purpose_feature_groups.get(purpose_code_for_color, fg_unknown_purpose)
        marker.add_to(target_fg)

    # --- 7. レイヤーコントロールを追加 ---
    folium.LayerControl().add_to(m)
    """
    # --- 8. マップを保存または表示 ---
    map_output_path = 'dam_map_multi_purpose_color_popup.html'
    m.save(map_output_path)
    print(f"改良されたマップが '{map_output_path}' に保存されました。ブラウザで開いて確認してください。")
    """
    # Jupyter Notebook/Lab環境であれば、直接表示
    m

    >OutputMap< ▼DynamicMap▼


    /tmp/ipython-input-10-1949420287.py:149: UserWarning: color argument of Icon should be one of: {'white', 'cadetblue', 'lightgreen', 'lightred', 'darkpurple', 'purple', 'darkred', 'red', 'orange', 'lightblue', 'pink', 'darkgreen', 'green', 'black', 'lightgray', 'darkblue', 'blue', 'gray', 'beige'}.
      icon=folium.Icon(color=marker_color, icon='info-sign')
    
    Make this Notebook Trusted to load map: File -> Trust Notebook

    ダムの種類ごとにClasteringしたかったのですが、今回はここまでにして、次に進もうと思います。


    前回:https://shikuuk.blogspot.com/2025/06/python-folium-relearning6-from-may.html

    次回:https://shikuuk.blogspot.com/2025/06/python-folium-relearning8-from-may.html

    コメント