Claude Codeでfoliumの地図HTMLを作る — 緯度経度の取り違えを検査する
位置データのCSVからfoliumで地図HTMLを作る手順です。MarkerClusterとchoroplethの書き方、エラーにならない緯度経度の取り違えを検査で止める方法をまとめます。
foliumは、Pythonで整えたデータをLeaflet.jsの地図に載せ、1枚のHTMLとして保存できるライブラリです。店舗一覧や配送先のCSVを地図にしたいとき、Claude Codeに書かせると最初の1枚は数分でできます。
怖いのは、見た目がそれらしく仕上がったまま間違うことです。foliumは緯度と経度を逆に渡してもエラーを出さず、地図は海の真ん中や空白で開きます。この記事では、地図HTMLを作る最小構成、MarkerClusterとchoroplethの書き方、取り違えを検査で止める流れを順に示します。
foliumの地図HTMLを、Claude Codeに任せるときの分担
foliumとは、Pythonのデータ処理とLeaflet.jsの地図描画をつなぐライブラリです。ドキュメントには「Pythonでデータを加工し、Foliumを通してLeafletの地図に可視化する」とあります。出力はHTMLファイルなので、サーバーなしでブラウザに渡せます。地図サービスをMCPで呼んで経路まで出す構成はMapbox MCPの記事にあり、ここで扱う静的なHTMLとは役割が違います。
Claude Codeに任せやすいのは、CSVの列名を読み、folium.MapとMarkerを組み、m.save()で書き出すところです。人が握るべきなのは、次の3点です。
- どの列が緯度で、どの列が経度か
- 座標が日本国内の範囲に収まっているか
- choroplethで、データ側のキーとGeoJSON側のキーが同じ値か
Claude Codeは任意のディレクトリで動き、ファイルを読んでコマンドを実行できます。Common workflowsのページにも、コードに限らず資料やデータのフォルダーで使えると書いています。位置データのCSVが置かれたフォルダーで起動し、依頼と検証を同じセッションに載せる流れが自然です。
最小構成: CSVからMarkerClusterの地図を書く
サンプルとして、駅名と座標を持つstations.csvを使います。標高も載せたいなら、座標から標高を取る流れは国土地理院の標高APIの記事にあります。
name,lat,lon
東京駅,35.6812,139.7671
新宿駅,35.6896,139.7006
横浜駅,35.4658,139.6226環境はvenvに入れます。foliumのGetting startedはpip install foliumを案内しており、依存はbranca・Jinja2・Numpy・Requestsです。
python3 -m venv .venv
.venv/bin/pip install folium pandasClaude Codeへの依頼は、列の意味と出力先まで先に言い切ると手戻りが減ります。
stations.csvを読み、foliumで地図HTMLを作るbuild_map.pyを書いて。
lat列が緯度、lon列が経度。MarkerClusterにまとめ、
popupに駅名を出し、out/map.htmlに保存する。返ってくるコードは、概ね次の形になります(ドキュメントのサンプルに沿った例示です)。
import folium
import pandas as pd
from folium.plugins import MarkerCluster
df = pd.read_csv("stations.csv")
m = folium.Map(
location=[df["lat"].mean(), df["lon"].mean()],
zoom_start=10,
)
cluster = MarkerCluster().add_to(m)
for row in df.itertuples():
folium.Marker(
[row.lat, row.lon], # 順番は 緯度, 経度
popup=row.name,
).add_to(cluster)
m.save("out/map.html")folium.Mapのlocationは、ドキュメントの例どおり[緯度, 経度]の順です。手元のfolium 0.20.0でこのスクリプトを動かすと、m.get_bounds()は[[35.4658, 139.6226], [35.6896, 139.7671]]を返し、3駅を囲む範囲として正しく計算されました。
緯度経度の取り違えはエラーにならない
ここからが本題です。同じ環境で、緯度と経度を入れ替えて呼んでみました。
bad = folium.Map(location=[139.7, 35.6], zoom_start=10)
print(bad.location) # [139.7, 35.6]
folium.Marker([139.7, 135.0]).add_to(m) # これも例外なしfolium.Mapはそのまま[139.7, 35.6]を保持し、Markerの追加でも例外は出ませんでした。緯度の有効範囲は-90〜90度ですが、少なくともこの版では、範囲外の値でも保存まで進みます。ブラウザで開いて初めて、マーカーが画面のどこにも出ないと気づく流れです。
落とし穴
緯度経度を逆に読むと、地図が空白で開く
CSVの列名がx・yや座標1・座標2のように中立だと、Claude Codeは並びから推測するしかありません。日本の座標は緯度が約20〜46、経度が約122〜154なので、値の大小で判定できます。列の意味は依頼文で明示し、コードの側にも検査を置きます。
範囲チェックを先に書かせる
地図を作る前に座標を検査する関数を足し、通らなければ止まる形が堅実です。日本国内のデータなら次のように書けます。
def check_japan(df):
bad = df[
~df["lat"].between(20, 46) | ~df["lon"].between(122, 154)
]
if not bad.empty:
raise ValueError(f"範囲外の座標があります:\n{bad}")この関数は、緯度と経度が逆の行をすべて拾います。緯度が139のような値はbetween(20, 46)を通らないためです。海外のデータを混ぜるなら範囲を広げるか、国ごとに切り替えます。
さらに、保存後にm.get_bounds()を使って、地図が想定の領域に収まっているかを確かめます。get_bounds()は[[南, 西], [北, 東]]の形で返るので、先ほどの例のように緯度が先に並んでいれば正常です。
Claude Codeには、検査を書いたうえで実行させます。
build_map.pyの先頭でlat/lonの範囲を検査して、
範囲外ならValueErrorで止めて。わざと緯度と経度を
入れ替えたCSVでも実行して、止まることを確認して。「止まることの確認」まで頼むのがコツです。検査を書いて満足すると、実際には通ってしまう検査になりがちです。壊したデータで失敗する様子を見て、初めて検査が働いていると言えます。試験用の座標を大量に作るときは、Fakerのテストデータの記事のように本番の位置情報を読ませない流れが使えます。
MarkerCluster: 件数が増えたときの書き方
マーカーが数百を超えると、地図は点で埋まります。MarkerClusterは、近いマーカーをまとめて数字付きの丸で表示し、ズームすると展開します。上の最小構成のとおり、MarkerCluster().add_to(m)でグループを作り、Markerのadd_to先をmではなくそのグループにします。
つまずきやすいのが、このadd_to先です。マーカーをmに直接足すと、クラスタには含まれません。見た目はマーカーが出るので、まとまらないことに後で気づきます。
公式のユーザーガイドには、座標のリストをまとめて渡す書き方もあります。
MarkerCluster(
locations=list(zip(df["lat"], df["lon"])),
popups=df["name"].tolist(),
).add_to(m)locationsは(緯度, 経度)のペアのリスト、popupsは同じ順番の文字列リストです。ループでMarkerを足す書き方に比べて短く、行数の多いデータで読みやすくなります。
さらに点数が多いケースには、FastMarkerClusterがあります。ユーザーガイドではFastMarkerCluster(data=list(zip(lats, lons)))の形で使っており、dataに座標のリストを渡します。ポップアップなど個別の装飾を付けない代わりに、軽く描けます。装飾が必要か、件数がどのくらいかで使い分ける選択肢です。
choropleth: GeoJSONとデータを結ぶkey_on
choroplethは、都道府県や市区町村のような領域を、値に応じた色で塗る地図です。foliumではfolium.Choroplethに、領域の形を持つGeoJSONと、値を持つ表を渡します。Getting startedの例は次のとおりです。
folium.Choropleth(
geo_data=state_geo,
name="choropleth",
data=state_data,
columns=["State", "Unemployment"],
key_on="feature.id",
fill_color="YlGn",
fill_opacity=0.7,
line_opacity=0.2,
legend_name="Unemployment Rate (%)",
).add_to(m)引数の意味は次のとおりです。
| 引数 | 役割 |
|---|---|
geo_data | 役割領域の形を持つGeoJSON |
data | 役割値を持つDataFrame |
columns | 役割[キー列, 値列]の2つ |
key_on | 役割GeoJSON側でキーに当たる場所 |
fill_color | 役割色のパレット名 |
key_on="feature.id"は、GeoJSONの各featureのidを、表のキー列と突き合わせるという意味です。idではなくpropertiesの中に名前がある地図データなら、key_on="feature.properties.name"のように指定先を変えます。
日本の都道府県データを使うときは、ここが最大の落とし穴です。表側が「東京都」、GeoJSON側が「東京」や13というコードだと、突き合わせが合わず塗られません。ガイドによれば、データにない領域はnan_fill_colorとnan_fill_opacityで見え方を変えられます。たとえば灰色で塗るように設定すると、突き合わせに失敗した領域がひと目で分かります。
folium.Choropleth(
geo_data=geo,
data=df,
columns=["pref", "value"],
key_on="feature.properties.name",
fill_color="YlGn",
nan_fill_color="gray",
nan_fill_opacity=0.4,
).add_to(m)色の区切りはbinsで指定できます。ガイドにはbins=[3, 4, 9, 11]のように境界値を並べる例があります。値の分布が偏っているとき、既定の等分では色が1色に寄るので、binsを直す価値があります。
GeoJSONの座標は経度が先
GeoJSONの座標は[経度, 緯度]の順です。foliumのlocationが[緯度, 経度]であることと逆なので、混ぜて手書きすると取り違えます。手元で、東京付近の四角形を[[139.7, 35.6], [139.8, 35.6], ...]と経度先で書いたGeoJSONをfolium.GeoJsonに渡したところ、get_bounds()は[[35.6, 139.7], [35.7, 139.8]]と、緯度を先にして返しました。GeoJSON側の順序はfoliumが吸収するので、手で並べ替えず、GeoJSONはGeoJSONの順序のまま使うのが安全です。
地図HTMLを共有する前に確かめること
m.save("out/map.html")で出るのは1枚のHTMLファイルです。Jupyter Notebookなら、変数mを最後の行に書くだけで同じ地図を表示できます(Getting startedにある書き方)。ただしNotebookの外へ渡すときは、HTMLファイルが現場の位置情報をそのまま含む点に気をつけます。
popupに入れた店舗名や顧客名、マーカーの座標は、HTMLのソースを開けば誰でも読めます。個人の自宅に近い座標や、取引先の名前を載せた地図を社外に出すなら、列を絞ってから描画する処理をスクリプトに入れておくほうが安全です。Claude Codeには「popupに出す列はnameだけにして、他の列は地図に含めない」と明示して頼みます。
また、サンプルでは平均値をlocationに使っていますが、外れ値が1件あるだけで中心がずれ、ズームも合いません。範囲検査を通したあとでも、m.fit_bounds(m.get_bounds())で全点が収まる位置に合わせる手があります。
検査をスクリプトに閉じ込めて、Claude Codeに回させる
検査と描画を1本のbuild_map.pyにまとめておくと、あとの依頼が楽になります。データが更新されたら、Claude Codeに「build_map.pyを実行して、エラーが出たら原因を直して」と頼むだけです。
非対話で回すこともできます。Common workflowsには、標準入力をパイプで渡すclaude -pの例があります。
claude -p "build_map.pyを実行して、検査エラーなら原因を説明して" \
--allowedTools "Read,Bash"--allowedToolsは、ツールの承認を事前に与えるフラグです。Non-interactive modeの解説でも、claude -p "..." --allowedTools "Read,Edit,Bash"の形が紹介されています。地図の生成は「読む」「実行する」だけで足りるので、Editを外して許可を絞ると、検査エラーの原因調査のときに意図せずコードが書き換わることを防げます。
CLAUDE.mdには、地図づくりの約束事を短く置きます。
## 地図(folium)
- 緯度が先: folium.Map / Marker の座標は [lat, lon]
- 座標は保存前に check_japan で検査する
- GeoJSONの座標は [lon, lat] のまま扱い、手で並べ替えない
- choroplethはnan_fill_colorを必ず指定し、塗れない領域を見えるようにするこうしておくと、別のデータで依頼しても、同じ型で書かれた地図スクリプトが返ってきやすくなります。
切り分け早見表: 地図が期待どおり出ないとき
| 症状 | 疑う箇所 | 確かめ方 |
|---|---|---|
| マーカーが画面に出ない | 疑う箇所緯度経度の逆転 | 確かめ方get_bounds()の1つ目が緯度の範囲か |
| 地図が海の上で開く | 疑う箇所locationの平均を取る列の取り違え | 確かめ方df[["lat","lon"]].describe()で範囲を見る |
| 点がまとまらない | 疑う箇所add_to先がmのまま | 確かめ方MarkerClusterの変数に足しているか |
| 一部の領域が塗られない | 疑う箇所key_onとキー列の表記ゆれ | 確かめ方nan_fill_colorで灰色にして洗い出す |
| 全域が1色になる | 疑う箇所値の偏り | 確かめ方binsで区切りを指定する |
Claude Codeに原因を探させるときは、症状と、この表の「確かめ方」の出力をそのまま貼ると、推測ではなく実測に基づく修正が返ってきます。
まとめ
foliumの地図づくりで、Claude Codeが書くコード自体は難しくありません。つまずくのは、エラーにならない緯度経度の逆転と、choroplethのキーの不一致です。どちらも、範囲チェックとnan_fill_colorという小さな仕掛けで目に見える失敗に変えられます。
最初の依頼では列の意味を言い切り、2回目からは検査を書かせて壊したデータで止まることを確かめる。この順番で進めると、地図が白く開いてから原因を探す時間を減らせます。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。
- code.claude.com/docs/en/common-workflows
- code.claude.com/docs/en/headless
- python-visualization.github.io/folium/latest
- python-visualization.github.io/folium/latest/getting_started.html
- python-visualization.github.io/folium/latest/user_guide/plugins/marker_cluster.html
- python-visualization.github.io/folium/latest/user_guide/geojson/choropleth.html