トップ > 技術ナレッジのアーカイブ > Pythonで地図を描く ~Foliumで冒険マップを作ってみた~#プログラミング編

2024.03.19

Pythonで地図を描く ~Foliumで冒険マップを作ってみた~#プログラミング編

#Python #Folium

5.プログラム実装(輪郭・背景・塗りつぶし画像作成)


インポートファイル

先に定義しておきます。

	  
  1. import os
  2. import seaborn as sns
  3. import numpy as np
  4. import csv
  5. import pandas as pd
  6. import seaborn as sns
  7. import cv2
  8. import io
  9. import glob
  10. from PIL import Image
  11. import matplotlib
  12. import matplotlib.pyplot as plt
  13. from matplotlib import ticker
  14. import datetime
  15. import matplotlib.font_manager as fm #https://python-academia.com/matplotlib-font/
  16. import matplotlib.dates as mdates
  17. from matplotlib.gridspec import GridSpec
  18. import geopandas
  19. import folium
  20. from selenium import webdriver
  21. from selenium.webdriver.chrome.options import Options
  22. import chromedriver_binary
  23. import json
  24. from time import sleep as slp

緯度経度情報をもとに都道府県および市区町村の輪郭を表示

コード例とその説明は以下のとおりです。

  • Folium.Map()関数では、地図の中心点の緯度経度のリストを引数locationとして渡します。
    今回locationは静岡県北端付近に設定しました。tilesには背景の地図の表示形式として、’cartodbposition’など10種類ほど(または何も表示しない’None’)を設定できます。
    zoom_startパラメータでは地図の表示範囲指定ができます(参照元)。
  • Folium.Map()関数をベースマップmとおくと、foliumの関数などで追加設定をしたあと、add_to(m)することで、重畳表示したい追加の情報が下から上に反映されていきます。
  • Folium.GeoJson()関数では変数pref_geojsonで定義した緯度経度情報を用いて、ベースマップ上に緯度経度の点群を表示しました。
  • 追加情報含めた地図はsave関数でhtml形式にて保存できます。今回は、扱いやすさのため、保存したhtmlファイルをwebdriverでChromeの画面に映し、そのスクリーンショット画像をpngで保存するようにしました(Chromeの画面に映す際、インターネット接続ほぼ必須になります)。
  1. import (上に定義したすべてのインポートファイル)
  2. def map_visualization():
  3. pref_geojson = "N03-23_22_230101.geojson" # 静岡県の市町村緯度経度情報
  4. # 緯度経度情報読み込みと描画
  5. pref_location = [35.0, 138.1] # 表示する地図中心の緯度経度設定
  6. f = open(pref_geojson, 'r', encoding="utf-8_sig")
  7. pref_geojson = json.load(f) # jsonファイルをpythonオブジェクトとして読み込み
  8. m = folium.Map(location=pref_location,zoom_start=8.5) # ベースマップを作製
  9. folium.GeoJson(pref_geojson).add_to(m) # 地図上に緯度経度の点群を追加
  10. m.save('outline.html') # 追加情報含めてhtmlファイルとして保存
  11. browser = webdriver.Chrome() # Chromeを立ち上げる
  12. tmpurl = 'C:/AI/test/wedding_movie/outline.html'
  13. browser.get(tmpurl) # htmlファイルを開く
  14. slp(1)
  15. filename = 'outline.png'
  16. browser.save_screenshot(filename) # スクリーンショットを保存
  17. browser.quit()
  18. if __name__ == '__main__':
  19. map_visualization()

実行結果は以下の通りです。

都道府県の市区町村の境界部分の輪郭設定

以下の変更を行うことで、色が変化します(これ以降は紙面の都合上、変更した箇所だけ変更前後のコードを示します)。

変更前

  1. folium.GeoJson(pref_geojson).add_to(m)

変更後

  1. folium.GeoJson(pref_geojson,
  2. style_function=lambda x: {'color': 'aqua'}
  3. ).add_to(m)

実行結果は以下の通りです。

6.プログラムによる輪郭設定と背景画像のカスタマイズ

Folium.Map()関数のオプションであるtilesやattrの設定をすることで選択、カスタマイズができます。 (参照元.1)(参照元.2)
今回は以下①~③の3つの方法をご紹介します。

6.1 方法①~Foliumにビルトインされている地図タイルを使用する場合

変更前はこちら

  1. m = folium.Map( location=pref_location, zoom_start=8.5)

変更後はこちら

Foliumにビルトインされている地図タイルを使う場合、以下のようにtiles=’openstreetmap’, ‘cartodbpositron’などの名称の指定でタイルを変更することが可能です。

  1. m = folium.Map(location=pref_location,
  2. tiles='cartodbpositron', zoom_start=8.5)

方法①の実行結果は以下の通りです

6.2 方法②~ネット上の画像を使用する場合

Foliumにビルトインされていない地図タイルを使うこともできます。こちらの記事によると、folium.Map関数のオプションのtilesにpngファイル、attrに名称を入れるとFoliumにビルトインされていない地図を背景にできることが分かりました。その記事では国土地理院の地図を使っていました。

今回の目的に合いそうなゲーム画面の地形図に似たタイルが欲しく色々調べていたところ、参考サイトにかなり多くの地形図の素材が落ちていることが分かりました。以下はサイトの画面ですが、画面右の任意の地形図名を選択すると、地形図名に応じた地図が表示されます。
今回Esri.WorldImageryが条件に一番近そうだったので、これを使うことにしました。サイト画面に”Plain JavaScript”の欄があり、
'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}'
という記述があったので、pngを付けてtileに設定したところ無事読み込むことができました。

前置きが長くなりましたが変更前後のコードをこちらに示します。attrは適当につけましたが、任意の名前でよさそうです(ただし、つけないとエラーになります)。

方法②による変更前がこちら

  1. m = folium.Map(location=pref_location,
  2. tiles='cartodbpositron',zoom_start=8.5)

方法②による変更後

  1. m =
  2. folium.Map(location=pref_location,tiles="https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}.png",
  3. attr='hogehoge',
  4. zoom_start=8.5)

実行した結果が以下になります。

6.3 動画で使う背景画像の作成

方法②の結果で表示された市区町村の水色の輪郭を消し、地形図のみを保存しました。保存名もworldimagery.pngに変更しました。変更前後のコードと結果を下記に示します。

変更前はこちら

  1. m = folium.Map(location=pref_location,
  2. tiles="https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}.png",
  3. attr='hogehoge',
  4. zoom_start=8.5)
  5. folium.GeoJson(pref_geojson,
  6. style_function=lambda x: {'color': 'aqua'}
  7. ).add_to(m)
  8. m.save('outline.html') # 追加情報含めてhtmlファイルとして保存
  9. browser = webdriver.Chrome() # Chromeを立ち上げる
  10. tmpurl = 'C:/AI/test/wedding_movie/outline.html'
  11. browser.get(tmpurl) # htmlファイルを開く
  12. slp(1)
  13. filename = 'outline.png'

変更後はこちら

  1. m = folium.Map(location=pref_location,
  2. tiles="https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}.png",
  3. attr='hogehoge',
  4. zoom_start=8.5)
  5. m.save('outline.html') # 追加情報含めてhtmlファイルとして保存
  6. browser = webdriver.Chrome() # Chromeを立ち上げる
  7. tmpurl = 'C:/AI/test/wedding_movie/outline.html'
  8. browser.get(tmpurl) # htmlファイルを開く
  9. slp(1)
  10. filename = 'worldimagery.png'

実行結果はこちら

6.4 方法③~自前で用意した画像を使用する場合

方法②のようなネット上の画像ではなく、自前で用意した画像ファイルを使うこともできます。今回のゲーム画面では、地図情報が解放されていないエリアは濃い青の下地にグリッドが入った背景が使われていますが、これに似せた以下の画像「map_example.jpg」を使ってゲーム画面に近づけることもできました。

map_example.jpg map_example.jpg

方法③による変更前はこちら

  1. m = folium.Map(location=pref_location,
  2. tiles="https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}.png",
  3. attr='hogehoge',
  4. zoom_start=8.5)
  5. m.save('outline.html') # 追加情報含めてhtmlファイルとして保存
  6. browser = webdriver.Chrome() # Chromeを立ち上げる
  7. tmpurl = 'C:/AI/test/wedding_movie/outline.html'
  8. browser.get(tmpurl) # htmlファイルを開く
  9. slp(1)
  10. filename = 'worldimagery.png'

方法③による変更後はこちら

  1. m = folium.Map(location=pref_location,
  2. tiles="map_example.jpg",
  3. attr='hogehoge',
  4. zoom_start=8.5)
  5. # 静岡県の市区町村の境界を表示
  6. folium.GeoJson(pref_geojson,
  7. style_function=lambda x: {'color': 'aqua'}
  8. ).add_to(m)
  9. m.save('outline.html') # 追加情報含めてhtmlファイルとして保存
  10. browser = webdriver.Chrome() # Chromeを立ち上げる
  11. tmpurl = 'C:/AI/test/wedding_movie/outline.html'
  12. browser.get(tmpurl) # htmlファイルを開く
  13. slp(1)
  14. filename = 'outline.png'

方法③を実行した結果がこちら

6.5 動画で使う輪郭画像の作成

方法③では静岡県の市区町村の境界を表示していましたが、追加で日本地図の各県の境界も表示させ、保存しました。保存名は方法③まで手順を踏んだ場合outline.pngになっているはずです。変更前後のコードと結果を下記に示します。

変更前はこちら

  1. #背景の表示
  2. m = folium.Map(location=pref_location,
  3. tiles="map_example.jpg",
  4. attr='hogehoge',
  5. zoom_start=8.5)
  6. #静岡県の市区町村の境界を表示
  7. folium.GeoJson(pref_geojson,
  8. style_function=lambda x: {'color': 'aqua'}
  9. ).add_to(m)
  10. m.save('outline.html') # 追加情報含めてhtmlファイルとして保存

変更後はこちら

  1. #背景の表示
  2. m = folium.Map(location=pref_location,
  3. tiles="map_example.jpg",
  4. attr='hogehoge',
  5. zoom_start=8.5)
  6. # 静岡県の市区町村の境界を表示
  7. folium.GeoJson(pref_geojson,
  8. style_function=lambda x: {'color': 'aqua'}
  9. ).add_to(m)
  10. # 日本地図の県境を表示
  11. pref_geojson = "japan.geojson" # 日本の各県の境界の緯度経度
  12. f = open(pref_geojson, 'r', encoding="utf-8_sig")
  13. pref_geojson = json.load(f) # jsonファイルをpythonオブジェクトとして読み込み
  14. folium.GeoJson(pref_geojson,
  15. style_function=lambda x: {'color': 'aqua'}
  16. ).add_to(m)
  17. m.save('outline.html') # 追加情報含めてhtmlファイルとして保存

実行した結果がこちらになります。

7.プログラムによる地図上の塗りつぶし

7.1 地図上のエリアの塗りつぶし

輪郭設定、背景画像のカスタマイズではfolium.Map()関数とfolium.GeoJson()関数の変更を行ってきました。
ここではfolium.GeoJson()関数を消し、エリア塗りつぶしのためfolium.Choropleth()関数を追加します。また、わかりやすさのためfolium.Map()関数に設定していたタイルをNoneとし、背景を無色にします。

変更前はこちら

  1. #背景の表示
  2. m = folium.Map(location=pref_location,
  3. tiles="map_example.jpg",
  4. attr='hogehoge',
  5. zoom_start=8.5)
  6. # 静岡県の市区町村の境界を表示
  7. folium.GeoJson(pref_geojson,
  8. style_function=lambda x: {'color': 'aqua'}
  9. ).add_to(m)
  10. # 日本地図の県境を表示
  11. pref_geojson = "japan.geojson" # 日本の各県の境界の緯度経度
  12. f = open(pref_geojson, 'r', encoding="utf-8_sig")
  13. pref_geojson = json.load(f) # jsonファイルをpythonオブジェクトとして読み込み
  14. folium.GeoJson(pref_geojson,
  15. style_function=lambda x: {'color': 'aqua'}
  16. ).add_to(m)
  17. m.save('outline.html') # 追加情報含めてhtmlファイルとして保存
  18. browser = webdriver.Chrome() # Chromeを立ち上げる
  19. tmpurl = 'C:/AI/test/wedding_movie/outline.html'
  20. browser.get(tmpurl) # htmlファイルを開く
  21. slp(1)
  22. filename = 'outline.png'

変更後はこちら

  1. #背景の表示
  2. m = folium.Map(location=pref_location,
  3. tiles=None,
  4. attr='hogehoge',
  5. zoom_start=8.5) # "Stamen Terrain",
  6. folium.Choropleth(geo_data=pref_geojson).add_to(m) #エリア塗りつぶし
  7. m.save('outline.html')
  8. browser = webdriver.Chrome() # Chromeを立ち上げる
  9. tmpurl = 'C:/AI/test/wedding_movie/outline.html'
  10. browser.get(tmpurl) # htmlファイルを開く
  11. slp(1)
  12. filename = 'choropleth.png'

実行した結果がこちらになります。すべての県が塗りつぶされました。

7.2 地図上の特定のエリアの塗りつぶしと色設定(動画で使う塗りつぶし画像の作成) 

ここでは一つの市町村だけ塗ってみます。市区町村コード一覧のサイトを参考に静岡県の5桁の自治体コード(code)とそれに対応する値(value)のリストを作り、特定のvalue(ここでは静岡市葵区)のみ値を1にします。このリストをcsvファイル(Shizuoka_visited.csv)として保存し、読み込むようにします。

Shizuoka_visited.csvの中身
number city code value
1 静岡市 22100  
2 葵区 22101 1
3 駿河区 22102  
4 清水区 22103  
5 浜松市 22130  
6 中区 22131  
7 東区 22132  
8 西区 22133  
9 南区 22134  
10 北区 22135  
11 浜北区 22136  
12 天竜区 22137  
13 沼津市 22203  
14 熱海市 22205  
15 三島市 22206  
16 富士宮市 22207  
17 伊東市 22208  
18 島田市 22209  
19 富士市 22210  
20 磐田市 22211  
21 焼津市 22212  
22 掛川市 22213  
23 藤枝市 22214  
24 御殿場市 22215  
25 袋井市 22216  
26 下田市 22219  
27 裾野市 22220  
28 湖西市 22221  
29 伊豆市 22222  
30 御前崎市 22223  
31 菊川市 22224  
32 伊豆の国市 22225  
33 牧之原市 22226  
34 東伊豆町 22301  
35 河津町 22302  
36 南伊豆町 22304  
37 松崎町 22305  
38 西伊豆町 22306  
39 函南町 22325  
40 清水町 22341  
41 長泉町 22342  
42 小山町 22344  
43 吉田町 22424  
44 川根本町 22429  
45 森町 22461  

csvファイルを読み込むための処理をm=folium.Map()関数の直前に追加し、特定の市区町村を特定の色で塗りつぶすためにfolium.Choropleth()関数の引数を追加しました。追加した引数のいくつかについて補足します。

  • geo_dataは静岡県の市区町村の境界の緯度経度、columnsはcodeとvalueを指定しました。
  • Key_onにはgeojsonファイルにあるデータとデータセットのデータを紐づけるキーとしてproperties.N03_007を記入しました。
  • fill_colorにはmatplotlibのカラーマップを指定することで、カラーバーの範囲が自動的に決まり、valueに対応する色で市区町村が塗りつぶされます。ここではRdPuを指定しました。
  • nan_fill_color=”transparent”を設定することで、csv内のvalueの値がない市区町村を透明にしました(何も指定しないと黒くなります)。

    Matplotlibのカラーマップ

変更前後のコードを下記に示します。ここまでの手順を踏んでいれば、変更後の塗りつぶし画像がchoropleth.pngで保存されるはずです。

変更前がこちら

  1. pref_geojson = json.load(f) # jsonファイルをpythonオブジェクトとして読み込み
  2. m = folium.Map(location=pref_location,
  3. tiles=None,
  4. attr='hogehoge',
  5. zoom_start=8.5) # "Stamen Terrain",
  6. folium.Choropleth(geo_data=pref_geojson).add_to(m) #エリア塗りつぶし
  7. m.save('outline.html')

変更前がこちら

  1. pref_geojson = json.load(f) # jsonファイルをpythonオブジェクトとして読み込み
  2. # 静岡県の自治体コードと各市区群に対応する値のリスト
  3. pref_data = pd.read_csv('shizuoka_visited.csv', encoding='utf-8_sig')
  4. pref_data['code'] = pref_data['code'].astype(str)
  5. print(pref_data[['code', 'value']])
  6. m = folium.Map(location=pref_location,
  7. tiles=None,
  8. attr='hogehoge',
  9. zoom_start=8.5) # "Stamen Terrain",
  10. folium.Choropleth(geo_data=pref_geojson,
  11. data=pref_data,
  12. columns=["code", "value"],
  13. key_on="properties.N03_007",
  14. fill_color="RdPu",
  15. nan_fill_color='transparent'
  16. ).add_to(m) # 塗分け
  17. m.save('outline.html')

実行結果がこちら。葵区だけ塗りつぶされました。

閉じる