今回はGeoPandasとFoliumによる
地理データの取り扱い方です。


解説動画はこちら


 
Pythonで地理データを取り扱う方法


今回は地理データを取り扱う方法として
2つのライブラリをご紹介します。


GeoPandas

Pandasライブラリに
空間(Geometry)を実現したライブラリ

Pandasの DataFrame を拡張
各行に 1つのジオメトリ(形状) を持つ
GIS(地理情報システム)向けの演算が可能
静的なplotが行える


Folium

Leaflet.js を
Python から操作する描画用ライブラリ

出力は HTML
OpenStreemMapをタイルレイヤとして表示できる
Jupyter / Web / 静的ファイルで使える
インタラクティブな描画が行える

ということでこの2つを用いて
地理データを描画していきましょう。



GeoPandasを用いた地理描画

最初はGeoPandasを用いた描画です
Google colabで実行できるコードになっていますが
Google colabでは日本語が取り扱えないので
別途ライブラリが必要です。


ライブラリのインストール

日本語を使用するため
japanize-matplotlib
をインストールしておきます。
pip install japanize-matplotlib


架空のデータを読み込んで描画する


まず初めは、何もない状態から
架空のデータを使っていきます。

pandasに読み込んだ後
GeoPandasに緯度経度として読み込みします。
# -----------------
# 1. データの準備 (架空のオープンデータ)
# -----------------

# (A) 地理データ: 都道府県庁所在地のポイントデータ (GeoJSONを想定)
# 緯度・経度と都道府県名が含まれるGeoDataFrameを作成
data_geo = {
    'city': ['Sapporo', 'Sendai', 'Tokyo', 'Nagoya', 'Osaka', 'Fukuoka'],
    'prefecture': ['北海道', '宮城', '東京', '愛知', '大阪', '福岡'],
    'latitude': [43.06, 38.27, 35.69, 35.18, 34.69, 33.59],
    'longitude': [141.35, 140.87, 139.69, 136.91, 135.50, 130.40]
}
df_geo = pd.DataFrame(data_geo)

# GeoDataFrameに変換
# 'geometry'カラムを作成し、緯度・経度からポイント(点)の地理情報を持たせる
gdf_city = gpd.GeoDataFrame(
    df_geo,
    geometry=gpd.points_from_xy(df_geo.longitude, df_geo.latitude),
    crs="EPSG:4326"
)
print("GeoDataFrameの確認:\n", gdf_city.head())
GeoDataFrameの確認:
       city prefecture  latitude  longitude              geometry
0  Sapporo        北海道     43.06     141.35  POINT (141.35 43.06)
1   Sendai         宮城     38.27     140.87  POINT (140.87 38.27)
2    Tokyo         東京     35.69     139.69  POINT (139.69 35.69)
3   Nagoya         愛知     35.18     136.91  POINT (136.91 35.18)
4    Osaka         大阪     34.69     135.50   POINT (135.5 34.69)


データに少し加工を加え、情報を足します。
# (B) 属性データ: 都道府県ごとの人口データ
# 都道府県ごとの人口属性データフレーム
data_attr = {
    'prefecture': ['北海道', '宮城', '東京', '愛知', '大阪', '福岡'],
    'population_mil': [5.2, 2.3, 14.0, 7.5, 8.8, 5.1], # 単位: 百万人
    'density_class': ['Low', 'Mid', 'High', 'High', 'High', 'Mid']
}
df_attr = pd.DataFrame(data_attr)

# -----------------
# 2. データの結合 (Merge)
# -----------------
# 'prefecture'カラムをキーにしてGeoDataFrameと属性データを結合
gdf_merged = gdf_city.merge(df_attr, on='prefecture')
print("\n結合後のGeoDataFrameの確認:\n", gdf_merged[['prefecture', 'population_mil', 'geometry']].head())
結合後のGeoDataFrameの確認:
   prefecture  population_mil              geometry
0        北海道             5.2  POINT (141.35 43.06)
1         宮城             2.3  POINT (140.87 38.27)
2         東京            14.0  POINT (139.69 35.69)
3         愛知             7.5  POINT (136.91 35.18)
4         大阪             8.8   POINT (135.5 34.69)


次のコードで簡易なプロットを行えます。
# 結合したデータを基にGeoPandasで簡易プロット
gdf_merged.plot(column='population_mil', legend=True, figsize=(5, 5),
                markersize=gdf_merged['population_mil'] * 10)
plt.title("GeoPandasによる簡易プロット (人口規模)")
plt.show()
スクリーンショット 2025-12-13 17.24.29

緯度経度だけを用いて描画していますが
これだけだと、あまり意味がない描画ですね

これをFoliumで描画しなおします。
# Foliumの描画準備 (日本の中心付近にマップを初期化)
map_center = [35.68, 139.69] # 東京の緯度・経度
m = folium.Map(location=map_center, zoom_start=5)

# -----------------
# 1. シンプルなマーカー表示 (庁所在地)
# -----------------
for idx, row in gdf_merged.iterrows():
    # ポップアップに表示する情報をHTMLで作成
    html = f"""
        

{row['prefecture']}庁所在地

人口: {row['population_mil']}百万人

密度区分: {row['density_class']}

""" iframe = folium.IFrame(html) popup = folium.Popup(iframe, min_width=200, max_width=300) # Markerを追加 folium.Marker( location=[row.latitude, row.longitude], popup=popup, icon=folium.Icon(color='blue', icon='info-sign') ).add_to(m) # ----------------- # 2. ヒートマップの追加 (人口密度に応じて色を濃く) # ----------------- from folium.plugins import HeatMap # HeatMapに必要なデータ形式: [[緯度, 経度, 強度], ...] # 強度として人口(百万)を使用 heat_data = [[row.latitude, row.longitude, row.population_mil] for idx, row in gdf_merged.iterrows()] HeatMap(heat_data).add_to(m) # ----------------- # 3. GeoPandasとFoliumを組み合わせた応用描画 # Foliumでは、CircleMarkerを使って、データを視覚的に表現できます。 def get_color(density): if density == 'High': return 'red' elif density == 'Mid': return 'orange' else: return 'green' for idx, row in gdf_merged.iterrows(): folium.CircleMarker( location=[row.latitude, row.longitude], radius=row['population_mil'] * 1.5, # 人口規模に応じて円の大きさを変更 color=get_color(row['density_class']), fill=True, fill_color=get_color(row['density_class']), fill_opacity=0.7, tooltip=f"{row['prefecture']}: {row['population_mil']}M" ).add_to(m) # マップをHTMLファイルとして保存 m.save("interactive_map.html") print("\nFoliumマップオブジェクトを作成しました。")

Foliumでは作った地図を
静的なHTMLとして保存ができます。

Google Colabでは
デフォルトのフォルダに
HTMLファイルが出力されると思います。

変数 m を実行すると
中身を描画することもできます。

スクリーンショット 2025-12-13 17.21.28

こんな感じで、架空のデータですが
地図上に描画できました。

Foliumはインタラクティブに操作できるので
色々遊べます。



500キロメートル圏内を描画する


GeoPandasで緯度経度から少し計算して
描画用のデータを作ることができます。

まずは距離を測るためのCRSというデータへ変換します。
# -----------------
# 1. CRSの変換 (距離計算のため)
# -----------------
# 緯度・経度 (4326) からメートル単位のCRS (3857) へ変換
gdf_projected = gdf_merged.to_crs("EPSG:3857")
print("\nCRS変換後のGeoDataFrame (EPSG:3857):\n", gdf_projected.head())
       city prefecture  latitude  longitude                          geometry  \
0  Sapporo        北海道     43.06     141.35  POINT (15735010.024 5321108.922)   
1   Sendai         宮城     38.27     140.87  POINT (15681576.668 4617638.286)   
2    Tokyo         東京     35.69     139.69  POINT (15550219.669 4258049.263)   
3   Nagoya         愛知     35.18     136.91  POINT (15240751.485 4188369.409)   
4    Osaka         大阪     34.69     135.50  POINT (15083791.002 4121832.777) 



これで計算する用意ができたので
Foliumにデータを加えます。
# -----------------
# 2. バッファリング (空間分析)
# -----------------
# 東京、大阪、名古屋の庁所在地から「500km圏内」のバッファを作成
# 単位はメートルなので 500 * 1000 = 500,000メートル
buffer_distance = 500 * 1000

# GeoPandasのbuffer()メソッドでバッファを作成
# わかりやすさのため、東京の行(index=2)のみを抽出
tokyo_buffer = gdf_projected.iloc[[2]].buffer(buffer_distance)

# バッファを元のCRS (4326) に戻し、Foliumで表示できるようにする
tokyo_buffer_wgs84 = tokyo_buffer.to_crs("EPSG:4326")

# -----------------
# 3. Foliumでの分析結果の可視化
# -----------------

# 新しいFoliumマップを作成
m_analysis = folium.Map(location=map_center, zoom_start=5)

# バッファリング結果 (Polygon) をFoliumに描画
folium.GeoJson(
    tokyo_buffer_wgs84.__geo_interface__, # GeoPandasオブジェクトをGeoJSON互換の形式に変換
    name='500km Buffer from Tokyo',
    style_function=lambda x: {
        'fillColor': 'purple',
        'color': 'purple',
        'weight': 2,
        'fillOpacity': 0.3
    }
).add_to(m_analysis)

# マーカーとヒートマップも再表示 (任意)
for idx, row in gdf_merged.iterrows():
    folium.Marker(
        location=[row.latitude, row.longitude],
        popup=f"{row['prefecture']} - {row['population_mil']}M"
    ).add_to(m_analysis)

# マップにレイヤー切り替え機能を追加して見やすくする
folium.LayerControl().add_to(m_analysis)
m_analysis.save("analysis_map.html")

今度は m_analysis という変数の中身を見てみると
スクリーンショット 2025-12-13 17.22.35

こんな感じで東京から500キロメートル圏内を
塗ることができました。



CSVファイルを読み込みして描画する


今度は架空ではなく
実際のデータで描画してみましょう。

地震のデータがあるので
これを用います。

地震データの取得先
https://www.data.jma.go.jp/eqdb/data/shindo/

このサイトから検索して
CSVファイルにして
Google Colabのファイル置き場に配置します。

CSVを読み込みします。
import pandas as pd
import folium
from folium.plugins import MarkerCluster
from io import StringIO
import re

# ----------------------------------------------------
# 1. データ準備
# ----------------------------------------------------
csv_data = """地震の発生日,地震の発生時刻,震央地名,緯度,経度,深さ,M,最大震度
2025/12/10,23:52:24.1,青森県東方沖,40°50.1′N,142°45.3′E,36 km,6.0,震度4
2025/12/09,18:09:45.9,青森県東方沖,41°16.0′N,142°25.1′E,47 km,5.3,震度3
2025/12/09,06:52:42.7,青森県東方沖,40°56.6′N,143°18.0′E,15 km,6.6,震度4
2025/12/09,03:56:29.5,青森県東方沖,40°57.0′N,143°07.6′E,19 km,6.1,震度3
2025/12/08,23:33:39.2,青森県東方沖,40°53.0′N,142°35.2′E,42 km,5.9,震度3
2025/12/08,23:15:10.1,青森県東方沖,40°58.0′N,142°17.2′E,54 km,7.5,震度6強
2025/12/05,12:11:24.1,熊本県阿蘇地方,32°58.5′N,131°06.8′E,4 km,3.3,震度3
"""
# df = pd.read_csv(StringIO(csv_data))
df = pd.read_csv('地震リスト.csv')
df.head(3)<
地震の発生日 地震の発生時刻 震央地名 緯度 経度 深さ 最大震度
0 2025/12/10 23:52:24.1 青森県東方沖 40°50.1′N 142°45.3′E 36 km 6.0 震度4
1 2025/12/09 18:09:45.9 青森県東方沖 41°16.0′N 142°25.1′E 47 km 5.3 震度3
2 2025/12/09 06:52:42.7 青森県東方沖 40°56.6′N 143°18.0′E 15 km 6.6 震度4


緯度経度などのデータが
そのままでは使えないので加工します。

def convert_dms_to_decimal(dms_str):
    """ '40°50.1′N' のような文字列を十進数形式に変換する関数 """
    match = re.match(r"(\d+)°(\d+\.?\d*)′([NSEW])", dms_str)
    if not match:
        return None
    
    degrees = float(match.group(1))
    minutes = float(match.group(2))
    direction = match.group(3)
    decimal = degrees + minutes / 60
    
    # 南緯(S)と西経(W)はマイナスにする
    if direction in ('S', 'W'):
        decimal *= -1  
    return decimal

# 緯度と経度を数値に変換
df['緯度_dec'] = df['緯度'].apply(convert_dms_to_decimal)
df['経度_dec'] = df['経度'].apply(convert_dms_to_decimal)

# 震度を強度順に数値化するための辞書
shindo_mapping = {
    '震度1': 10,
    '震度2': 20,
    '震度3': 30,
    '震度4': 40,
    '震度5弱': 51,
    '震度5強': 55,
    '震度6弱': 61,
    '震度6強': 65,
    '震度7': 70,
}

# 震度を数値に変換
df['shindo_value'] = df['最大震度'].map(shindo_mapping)

def get_shindo_color(shindo):
    # 震度値 (30, 40, ..., 70) を利用
    if shindo >= 65:  # 震度6強, 震度7
        return 'darkred'
    elif shindo >= 55: # 震度5強, 震度6弱
        return 'red'
    elif shindo >= 51: # 震度5弱
        return 'orange'
    elif shindo >= 40: # 震度4
        return 'lightred'
    elif shindo >= 30: # 震度3
        return 'green'
    else:
        return 'gray' # その他

描画を行います。
# ----------------------------------------------------
# 1. Folium マップの作成
# ----------------------------------------------------
# 日本の中心付近を初期位置とする
map_center = [df['緯度_dec'].mean(), df['経度_dec'].mean()]
m = folium.Map(location=map_center, zoom_start=5)

# ----------------------------------------------------
# 2. 震度ごとに FeatureGroup (レイヤー) を作成
# ----------------------------------------------------

# 震度レベルごとの FeatureGroup を格納する辞書
shindo_layers = {}

# 震度マッピングのキー(例: '震度7', '震度6強')を降順でソート
sorted_shindo_levels = sorted(shindo_mapping.keys(), key=lambda x: shindo_mapping[x], reverse=True)

for shindo_level in sorted_shindo_levels:
    # レイヤー名を設定 (LayerControlに表示される名前)
    layer_name = f"最大震度: {shindo_level}"
    # FeatureGroupを作成し、マップに追加
    fg = folium.FeatureGroup(name=layer_name, show=True).add_to(m)
    shindo_layers[shindo_level] = fg

# ----------------------------------------------------
# 3. データの反復処理と適切なレイヤーへのマーカー追加
# ----------------------------------------------------
for idx, row in df.iterrows():
    datetime_full = f"{row['地震の発生日']} {row['地震の発生時刻'][:8]}"
    
    # ポップアップ情報
    popup_html = f"""
        **最大震度: {row['最大震度']}**
発生日時: {datetime_full}
震央地名: {row['震央地名']}
深さ: {row['深さ']}
マグニチュード(M): {row['M']}
""" marker_color = get_shindo_color(row['shindo_value']) current_shindo_level = row['最大震度'] # 該当する震度の FeatureGroup を取得 target_fg = shindo_layers.get(current_shindo_level) if target_fg is not None: # マーカーを対応する FeatureGroup に追加 folium.Marker( location=[row['緯度_dec'], row['経度_dec']], popup=popup_html, icon=folium.Icon(color=marker_color, icon='flash', prefix='fa') ).add_to(target_fg) # ---------------------------------------------------- # 4. LayerControl (表示切り替えボックス) を追加 # ---------------------------------------------------- folium.LayerControl(collapsed=False).add_to(m) # ---------------------------------------------------- # 5. マップの保存と表示 # ---------------------------------------------------- m.save("earthquake_layer_control_map.html") print("震度別表示切り替え機能付きFoliumマップの作成が完了しました。")

変数 m を表示してみると
スクリーンショット 2025-12-13 17.21.43


震度ごとに表示を切り替えすることができます。





まとめ

GeoPandasで計算して、Foliumで描画すると
以下のような分析が簡単に行えます。

商圏分析
人口ヒートマップ
配送ルート可視化
不動産マップ
災害・危険区域表示

これらの分析を行う際に
このライブラリの組み合わせは
非常に捗ります。

かなり便利なので
使ってみると面白いと思います
それでは。