Python Folium Relearning(1) from May 2025(国土数値情報行政区域情報による都道府県別日本地図の描画)

PythonのGIS Visualize Libraryの1つであるFoliumについてFoliumのHPの教材を参考にしてGoogleColabで徹底的に再学習します。

Folium URL: https://python-visualization.github.io/folium/latest/index.html

最初にFoliumのimportでございます。

# Get start Folium
!pip install folium
import folium

まずはDefaultMapをDrawしました。TargetはMt. Fuji that all Japanese Prizeです。一応、map.save('map.html')で作成したmapをCurrentDirectryに保管したうえで、mapをDrawしています。

map = folium.Map(location=(35.3606, 138.7275), zoom_start = 12) # Arround Mt. Fuji, 'zoom_start' is defined the scale that the map initially drawn.
map.save("map.hlmt")
map # Show the map

Draw MapのResultはリンク先のようになります。
いわゆるOpenStreetMapが表示されております。

OpenStreetMapについてGemini先生に聞いてみると以下のような回答をいただきました。

OpenStreetMap(OSM)とは?

OpenStreetMap (OSM) は、世界中のボランティアの貢献によって作成・維持されている、誰でも自由に利用できるオープンソースの地図データです。2004年にイギリスで始まり、「地図データが自由に使えない」という状況を変えたいという思いからスタートしました。

OSMの主な特徴

  • オープンデータ: OSMのデータは「Open Database License (ODbL)」というオープンライセンスで公開されており、商用・非商用問わず、誰でも無償で利用、改変、再配布できます。これは、他の多くの地図サービスと大きく異なる点です。
  • コミュニティ主導: 世界中の「マッパー」と呼ばれるボランティアが、GPSデータ、航空写真、地元の知識などを基に地図情報を追加・編集しています。
  • 多様なデータ: 道路、建物、POI(Point of Interest:店舗、施設など)、自然地物、交通機関など、非常に多岐にわたる情報が収録されています。
  • リアルタイム性: ボランティアによって日々更新されているため、新しい道路や建物の情報などが比較的早く反映されることがあります。
  • 多言語対応: 地図データだけでなく、インターフェースも多言語に対応しており、世界中のどこでも利用しやすいのが特徴です。

OSMの利用方法

OSMのデータは様々な方法で利用できます。

  • ウェブサイトでの閲覧: https://www.openstreetmap.org/ にアクセスすれば、誰でも無料で世界中の地図を閲覧できます。松山の街並みも詳細に見ることができますよ。
  • 地図アプリでの利用: OSMのデータを利用したナビゲーションアプリや地図アプリが多数存在します。例えば、 OsmAnd や Maps.me などがあります。
  • 地理空間情報の分析: GIS (地理情報システム) ソフトウェアでOSMのデータを読み込み、地域の分析やハザードマップの作成などに活用できます。
  • ウェブサイトやアプリへの組み込み: Leaflet (以前 Folium の話題で出てきましたね!) などのJavaScriptライブラリを使うと、OSMのタイルを自分のウェブサイトやアプリに簡単に組み込むことができます。
  • データの編集: もし松山で見慣れない道や新しくできたお店などがあれば、あなたもマッパーとしてOSMの編集に参加できます。ウェブサイト上でアカウントを作成し、簡単なチュートリアルに従えばすぐに始められます。

OSMの歴史

2004年にスティーブ・コースト氏によって始められたOSMは、多くのボランティアの協力によって成長してきました。当初はヨーロッパが中心でしたが、徐々に世界中に広がり、現在では200万人以上のアカウントを持つ活発なコミュニティとなっています。

OSMのライセンス

OSMのデータは、前述の通り「Open Database License (ODbL)」というオープンライセンスで提供されています。これは、データの自由な利用、改変、再配布を認める一方で、派生したデータセットも同様のライセンスで公開することを求める「コピーレフト」の性質を持っています。


 Foliumでは下敷きになるMapのことをTilesetと呼んでいます。 Tilesetにはいろいろな種類があります。OpenStreetMapのことはこの辺にしてCivil Engineerであるわたしにとってなじみ深い国土地理院の地図も表示可能です。

# 国土地理院の地図タイルURL(電子地形図20万)
tileset_url = "https://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png"

# 地図の作成(例: 高松市中心を緯度経度で指定)
map2 = folium.Map(location=[35.3606, 138.7275], # Arround Mt. Fuji Summit
        tiles=tileset_url,
        attr="国土地理院",  # 著作権表記(任意)
        name="国土地理院地図",  # レイヤー名(任意)
        zoom_start = 12)

# マップを表示
map2

Outputは以下のリンク先になります。


等高線が表示されると、'The Mount Fuji'という雰囲気があふれ出します。ちなみにコードの中に記載している'attr="国土地理院"'というのは地図の右下隅に「国土地理院」という著作権表示をするためのコードです。 次に、富士山山頂付近と山中湖付近にMakerを表示してみます。

folium.Marker( # Create Marker
    location=[35.3606, 138.7275], # LonLat of the Mt. Fuji Summit
    tooltip="Click me !", # Show the massage when mouse pointer close the marker
    popup="Mt. Fuji Summit", # Show the massage when marker is clicked
    icon=folium.Icon(icon="cloud", color="red"),
).add_to(map2)

folium.Marker(
    location=[35.41667, 138.87500], # LonLat of the Lake Yamanaka
    tooltip="Click me!",
    popup="Lake Yamanaka",
    icon=folium.Icon(color="blue"),
).add_to(map2)

map2

結果はリンク先のようになりました。山中湖は最初の地図(zoom=12)の外側になっていますので、初期画面の設定をzoom=10に変更しています。富士山の山頂に赤色の雲Makerが、山中湖あたりに青色のiMarkerが表示されています。各Markerの近くにMousePointerを近づけると'Click me!'というMassageが表示されて、Clickすると'Mt Fuji Summit'などが表示されます。 

次にLineをDrawしてみます。田貫湖(35.34417, 138.56083)、本栖湖(35.4740898, 138.5869733)、西湖(35.497999, 138.686166)、河口湖(35.517778, 138.756111)、山中湖(35.41667, 138.87500)をLineでConnectします。 'tooltip=' ''はLine付近にMousePointerがCloseしたときのShownMassageになります。

trail_coordinates = [
    (35.34417, 138.56083), # Tanuki Lake
    (35.4740898, 138.5869733), # Motosu Lake
    (35.497999, 138.686166), # Saiko Laku
    (35.517778, 138.756111), # Kawaguchi Lake
    (35.41667, 138.87500) # Yamanaka Lake
]

folium.PolyLine(trail_coordinates, tooltip="Lake Conected Line").add_to(map2)

map2

結果は以下のリンク先のとおりです。

次は少し趣向を変えて、富士山頂周辺にランダムに2つのグループの座標点を作って、グループ別にMarkerを表示する方法です。 まずは、与点(今回は富士山頂上の緯度経度)を中心に、一定のばらつき(今回は±0.1度)の範囲内でランダムな座標点を10個作り、それを2回繰り返して、1セットあたり10個の点を持つ2つの点群を作成するコードになります。

import random

# Coordinate of Mt. Fuji Summit
fuji_summit_latlon = (35.3606, 138.7275)

# a number of create points
num_points = 10

# a range of change lat/lon
lat_range = 0.1
lon_range = 0.1

# List of the create points
all_random_points = []

for _ in range(2):
    random_points = []

    for _ in range(num_points):
        # Creat the random offset about lat/lon
        lat_offset = random.uniform(-lat_range, lat_range)
        lon_offset = random.uniform(-lon_range, lon_range)

        # Calculate the new lat/lon
        rand_latlon = (fuji_summit_latlon[0] + lat_offset, fuji_summit_latlon[1] + lon_offset)

        # Appnd the new point to random_points
        random_points.append(rand_latlon)

    all_random_points.append(random_points)

for random_points in all_random_points:
    print(random_points)

出力は以下の通りです。[ ]で囲まれた点群リストが2つあり、[ ]の中には( )で囲まれた緯度経度タプルになっています。
「[(35.40769227754371, 138.65111198057184), (35.324883229207515, 138.74899086974895), (35.390284087703364, 138.65241989693942), (35.43548020554178, 138.6515310342773), (35.413523849187285, 138.82061630610156), (35.44654248823342, 138.8173222191712), (35.36625592465124, 138.81750041425113), (35.41206666508531, 138.63039087393713), (35.37051186352305, 138.70180734050493), (35.38052055865246, 138.6968948257293)]
[(35.318744451330076, 138.65545036103288), (35.377565690267645, 138.63439116292457), (35.41149420825053, 138.72373338266755), (35.43149769190481, 138.66586001516328), (35.30788772151855, 138.74758772114785), (35.39147273281529, 138.7694903859327), (35.30793159705596, 138.75568045138186), (35.322297680725804, 138.79933001577626), (35.4596069258978, 138.80579857340834), (35.44558397877945, 138.68520057163818)]」
この2つの点群を、2種類のMarkerを使って、2つのLayerに表示します。別のLayerに表示するということは、片方だけを表示、あるいは両方を表示、全部非表示という選択ができるようになります。 せっかくなのでMarker TypeもTry To Changeします。

二重書きになりますが、国土地理院地図の読み込みからコードを記載しています。
# 国土地理院の地図タイルURL(電子地形図20万)
tileset_url = "https://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png"

# 地図の作成
map2 = folium.Map(location=[35.3606, 138.7275], # Arround Mt. Fuji Summit
        tiles=tileset_url,
        attr="国土地理院",  # 著作権表記(任意)
        name="国土地理院地図",  # レイヤー名(任意)
        zoom_start = 12)

marker_color = ["lightred", "purple"]
marker_group = ["first_group", "second_group"]
marker_type = ["star", "home"]

for i, random_points in enumerate(all_random_points):
    group = folium.FeatureGroup(marker_group[i]).add_to(map2)

    for latlon in random_points:
        folium.Marker(latlon, icon=folium.Icon(icon = marker_type[i], color=marker_color[i])).add_to(group)


folium.LayerControl().add_to(map2)

map2


結果は下の地図のようになります。見やすいようにzoom=11に修正しました。
地図の右上にあるLayerをControlするBoxをクリックして'first group', 'second group'のチェックボックスを外すと、それぞれのlight redのstar markerと、purpleのhome markerが消滅します。チェックボックスにcheckを入れれば、再び該当マーカーが表示されます。
Dynamic HTML Map

Make this Notebook Trusted to load map: File -> Trust Notebook
このSectionの最後にGeoJsonあるいはTopJsonを使用して日本地図に県境を表示するコードを作成してみます。まずはFoliumHomePageに掲載されている世界地図に国境を表示するコードを実行してみます。
!pip install folium
import folium
import requests

m = folium.Map(tiles="cartodbpositron")

geojson_data = requests.get(
    "https://raw.githubusercontent.com/python-visualization/folium-example-data/main/world_countries.json"
).json()

folium.GeoJson(geojson_data, name="hello world").add_to(m)

folium.LayerControl().add_to(m)

m
出力は以下のとおりになります。ちなみに'm = folium.Map(tiles="cartodbpositron")'はOpenStreetMapの白地図になります。たったこれだけのコードで世界の国々の国境を描けるというのは感動です。

Make this Notebook Trusted to load map: File -> Trust Notebook
ここからは日本地図版のオリジナルのコードになります。都道府県の境界のデータは国土数値情報-行政区分データ(URL:https://nlftp.mlit.go.jp/ksj/index.html)を使用します。国土数値情報のダウンロード画面をコピペします。
真ん中右端にある行政区分をクリックして、下方にスクロールしていくと、次の画面が現れます。

今回は、現時点の最新情報である2024年(令和6年)のデータ「N03-20240101_GML.zip」を使います。データの中身を見るために、ダウうロードして解凍すると、以下のようなファイルが格納されています。
GeoJson形式もありますが、わたしはShape形式を使います。しかも、N03-20240101.shpとN03-20240101_prefecture.shpという2種類のFileがありますが、その中身を見てみると、つぎのようになっていました。1つ目がN03-20240101.shp、2つめがN03-20240101_prefecture.shpです。

自治体の名称があるかorないかという違いかなぁ…。とりあえず自治体の名称が入っているN03-20240101.shpを使おうと思います。
あとさきになりますが、国土数値情報から行政区域データをWebScrapingでZipFileを取得して解凍し、必要なN03-20240101.shpを読み込んでGeoDataFrame形式に変換するコードです。
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/N03/N03-2024/N03-20240101_GML.zip'
zip_file_name = 'N03-20240101_GML.zip'
shp_file_name = 'N03-20240101.shp'

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

# データを表示
gdf
このままだと自治体ごとで別々のデータになってしまうので、47都道府県別にPolygonDataを結合します。このコードはGemini先生に教えてもらいました。
from shapely.ops import unary_union

# 都道府県コードのカラム名を指定(例として 'N03_001' を使用)
pref_code_column = 'N03_001'

# 都道府県コードでグループ化し、各グループのgeometryをunary_unionで結合
prefecture_gpolygon = gdf.groupby(pref_code_column)['geometry'].apply(unary_union)
prefecture_gdf = gpd.GeoDataFrame(prefecture_polygons, geometry=prefecture_polygons)
prefecture_gdf.index.name = '都道府県名'

# 結果をGeoDataFrameに変換
prefecture_gdf = gpd.GeoDataFrame(prefecture_polygons, geometry=prefecture_polygons)

# インデックス名を都道府県コードに戻す
prefecture_gdf.index.name = pref_code_column

# 結果のGeoDataFrameを表示
print(prefecture_gdf)
出力は以下のとおりです。
geometry N03_001 三重県 MULTIPOLYGON (((136.02156 33.7343, 136.02154 3... 京都府 MULTIPOLYGON (((134.89358 35.64858, 134.89358 ... 佐賀県 MULTIPOLYGON (((129.78926 33.44452, 129.7893 3... 兵庫県 MULTIPOLYGON (((134.38287 34.71978, 134.38288 ... 北海道 MULTIPOLYGON (((139.34092 41.49493, 139.34094 ... 千葉県 MULTIPOLYGON (((139.76374 34.95555, 139.76374 ... 和歌山県 MULTIPOLYGON (((135.01073 34.27546, 135.01069 ... 埼玉県 MULTIPOLYGON (((139.38154 35.7745, 139.38152 3... 大分県 MULTIPOLYGON (((131.87481 32.74963, 131.875 32... 大阪府 MULTIPOLYGON (((135.12099 34.27391, 135.12083 ... 奈良県 POLYGON ((135.62219 33.87319, 135.62191 33.873... 宮城県 MULTIPOLYGON (((140.74745 37.78668, 140.74711 ... 宮崎県 MULTIPOLYGON (((131.22784 31.42304, 131.22781 ... 富山県 MULTIPOLYGON (((136.84191 36.33123, 136.84167 ... 山口県 MULTIPOLYGON (((130.85991 33.97198, 130.8599 3... 山形県 MULTIPOLYGON (((139.5472 38.55305, 139.5472 38... 山梨県 POLYGON ((138.52308 35.2486, 138.52312 35.2478... 岐阜県 POLYGON ((136.67421 35.15833, 136.67426 35.158... 岡山県 MULTIPOLYGON (((133.51637 34.31983, 133.51636 ... 岩手県 MULTIPOLYGON (((141.71212 38.9362, 141.71212 3... 島根県 MULTIPOLYGON (((131.79167 34.43598, 131.79138 ... 広島県 MULTIPOLYGON (((132.3166 34.12325, 132.31661 3... 徳島県 MULTIPOLYGON (((134.31885 33.53871, 134.31889 ... 愛媛県 MULTIPOLYGON (((132.01232 33.33811, 132.01235 ... 愛知県 MULTIPOLYGON (((136.97287 34.69294, 136.97287 ... 新潟県 MULTIPOLYGON (((138.08367 36.83146, 138.08355 ... 東京都 MULTIPOLYGON (((136.07396 20.42276, 136.07388 ... 栃木県 POLYGON ((139.78513 36.22677, 139.78501 36.226... 沖縄県 MULTIPOLYGON (((123.01354 24.43677, 123.01353 ... 滋賀県 POLYGON ((136.00246 34.83315, 136.00266 34.833... 熊本県 MULTIPOLYGON (((129.96251 32.16882, 129.9625 3... 石川県 MULTIPOLYGON (((136.2521 36.30055, 136.25217 3... 神奈川県 MULTIPOLYGON (((139.16271 35.13767, 139.16272 ... 福井県 MULTIPOLYGON (((135.51492 35.49651, 135.51492 ... 福岡県 MULTIPOLYGON (((130.04209 33.47813, 130.0421 3... 福島県 MULTIPOLYGON (((140.49709 36.81922, 140.49682 ... 秋田県 MULTIPOLYGON (((139.69204 39.93446, 139.692 39... 群馬県 POLYGON ((138.73301 35.99911, 138.73259 35.998... 茨城県 MULTIPOLYGON (((140.375 35.90218, 140.37398 35... 長崎県 MULTIPOLYGON (((128.35065 31.96716, 128.35065 ... 長野県 POLYGON ((137.8953 35.25784, 137.89523 35.2577... 青森県 MULTIPOLYGON (((139.50044 40.53221, 139.50041 ... 静岡県 MULTIPOLYGON (((138.14554 34.61213, 138.14554 ... 香川県 MULTIPOLYGON (((133.45422 34.10376, 133.45417 ... 高知県 MULTIPOLYGON (((132.54146 32.70305, 132.54151 ... 鳥取県 MULTIPOLYGON (((133.25438 35.07346, 133.2542 3... 鹿児島県 MULTIPOLYGON (((128.40861 27.03296, 128.40901 ...

このデータは非常に細かい(相当ズームしても市町村境界がきれいに見えるレベル)なので、都道府県レベルで処理するにはFoliumの地図表示などに時間がかかるため、少しデータを間引くコードを挟みます。変数toleranceの数値を調整することで、データ量を調整できます。

from shapely.geometry import mapping
from shapely.ops import unary_union

# 形状を簡略化する (toleranceの値は調整が必要)
tolerance = 0.01  # 小さいほど元の形状に近いがデータ量は多い
prefecture_gdf['simplified_geometry'] = prefecture_gdf['geometry'].apply(lambda geom: geom.simplify(tolerance))


そして、このデータをFoliumのcartodbpositron地図上に表示します。
import folium

# 地図の中心となる緯度経度を設定
center_lat = 35.6895
center_lon = 139.6917

# Foliumの地図オブジェクトを作成
m = folium.Map(location=[center_lat, center_lon], zoom_start=4, tiles="cartodbpositron")

# 簡略化した形状をGeoJSON形式で追加
for index, row in prefecture_gdf.iterrows():
    geojson_geom = mapping(row.simplified_geometry)  # 簡略化したgeometryを使用

    folium.GeoJson(
        geojson_geom,
        style_function=lambda feature: {
            'fillColor': '#ff7800',
            'color': '#000000',
            'weight': 1,
            'fillOpacity': 0.6,
        },
        tooltip=index
    ).add_to(m)

# 地図を表示
m

以下のような地図が表示されます。

Make this Notebook Trusted to load map: File -> Trust Notebook


foliumの設定の内容についてGemini先生に聞きました。

このコードは、Pythonの地理空間情報可視化ライブラリであるfoliumを使用して、GeoJSON形式の地理データを地図mに追加するものです。それぞれのパラメータについて詳しく見ていきましょう。

  • geojson_geom: これは、描画したい地理データを含むGeoJSONオブジェクト(例えば、ポリゴン、ライン、ポイントなど)です。この変数は、事前に定義されている必要があります。
  • style_function=lambda feature: {...}: これは、各地物(feature)のスタイルを決定するための関数です。lambda式を使って、匿名関数を定義しています。この関数は、各地物のプロパティに基づいて動的にスタイルを設定するために使用できますが、この例ではすべての地物に同じスタイルを適用しています。
    • 'fillColor': '#ff7800': 地物の塗りつぶしの色を16進数カラーコード #ff7800(オレンジ色)で指定しています。
    • 'color': '#000000': 地物の境界線の色を16進数カラーコード #000000(黒色)で指定しています。
    • 'weight': 1: これが線の太さを指定するパラメータです。現在の値は 1 ピクセルです。
    • 'fillOpacity': 0.6: 地物の塗りつぶしの透明度を 0.0(完全に透明)から 1.0(完全に不透明)の範囲で指定しています。ここでは 0.6 なので、少し透明になっています。
  • tooltip=index: これは、各地物にマウスオーバーした際に表示されるツールチップの内容を指定しています。index という変数が、各地物に対応する何らかのインデックスや名前の情報を持っていると考えられます。
  • .add_to(m): 作成した GeoJSON レイヤーを、m という名前の folium.Map オブジェクトに追加しています。

第1回はここで終わります。第2回は次のURLです。

コメント