プログラミング開発

【folium】渋谷事変の時系列位置情報を可視化してみた(呪術廻戦)

Pythonのfoliumライブラリで位置情報を可視化できることを学んだ。その次に時系列データを地図上にマッピングして時間推移を見えるようにした。一通りの学習の成果として実践的な課題に取り組んでみたい。何か良い課題はないかな。。

こんな悩みにお答えします。

今回は、呪術廻戦の渋谷事変を参考にfoliumで時系列位置情報を可視化する方法をご紹介します。

この記事を読み終えると、foliumの適用イメージを身に付けることができ、自由に位置情報の可視化を行うことができるようになります。

本記事の内容
・呪術廻戦の渋谷事変の時系列位置情報を可視化する方法

>>【Python】位置情報(緯度・経度)を可視化する方法(答えはfoliumライブラリ)
>>【Python】foliumのマーカーアイコンを自由に変更する方法
>>【Python】foliumで時系列位置情報を可視化する方法(ヒートマップ・GIF)

Pythonを学べるおすすめのプログラミングスクール
1. Aidemy
・・・AIエンジニアとしてのスキルを身に付けたい。その延長で就職、転職できたら嬉しい方におすすめ。
2.侍エンジニア塾
・・・基礎力と即戦力を身に付けられる、満足度No.1スクール
3.
TechAcademy
・・・副業に向けてスキルアップしたい。フリーランスなどを目指している方向け。
4
. AIジョブカレッジ
・・・コスパ良く、質の高い講座を受けたい方におすすめ。

本記事の信頼性

ひよこ
ひよこ

私は、大学時代にプログラミングを学び、PHPなどのWeb系言語からAIに用いられるPythonまで幅広く経験してきました。

現在はデータサイエンティストとして大企業で活動しています。

また今ではほぼ不労所得として月10万円以上の収益を継続的に達成しています。

 

 

1. 出来上がりイメージ

今回の出来上がりイメージは上記のとおりです。

呪術廻戦の渋谷事変を時系列位置情報と捉えて可視化してみました。多少位置関係や時間帯が曖昧な箇所がありますが、おおよそ原作通りかと思います。

このくらいのクオリティで自由に可視化することができれば、大抵のやりたいことはfoliumですべてできるようになるのではないでしょうか。

 

本ブログでは度々「呪術廻戦」のキャラクターさんにお世話になっておりましたが、今回初めてストーリーにも踏み込ませていただくため、少し原作漫画をご紹介させていただければと思います。

a. 呪術廻戦

呪術廻戦とは・・・

呪い。
辛酸・後悔・恥辱…。人間の負の感情から生まれる
禍々しきその力は、人を死へと導く。

ある強力な「呪物」の封印が解かれたことで、
高校生の虎杖は、呪いを廻る戦いの世界へと入っていく…!

異才が拓く、ダークファンタジーの新境地!  (引用:公式HP

芥見下々先生の原作作品。週刊少年ジャンプにて2018年より連載されている大人気漫画。昨年2021年には映画も公開され、呪術廻戦の人気は留まることを知りません。

ひよこ
ひよこ

私自身、半年前くらいからの読者でまだまだ新参者ですが、全巻読み込みアニメも見てしまうくらいにはハマっています✨

b. 渋谷事変

渋谷事変は、10巻~16巻の実に7巻分をかけて描かれた呪術廻戦の中でも大きな山の一つ。多くの戦闘シーンや人気キャラの死など大きな話題を呼びました。

舞台は東京渋谷。実在する土地・建物まで忠実に描かれており、読者は臨場感を与えられたのではないでしょうか。

ひよこ
ひよこ

渋谷という舞台の上で物語が展開されたため、位置情報の可視化のテーマには持って来いと判断いたしました!

 

2. 実行環境

以降、実際にコーディングしていく上で、実行環境を記載いたします。

  • Windows10
  • Python:3.6.4
  • folium:0.12.1
  • selenium:3.141.0
  • PIL:8.3.1

 

3. 前準備

まずは前準備です。どのキャラクターがどこにいるのかを確認する必要があるため、正直この部分が最も時間がかかります。

・Excelファイル
・アイコン

a. Excelファイル

キャラクターごとに「いつ・どこにいるのか」の情報を記載します。カラムは以下のとおりです。

カラム概要
time時間
nameキャラクター名
lat緯度
lon経度

 

これを時間とキャラクターごとに何行も作成していきます。今回は約230行作成しました!(圧巻😝)

冒頭部分を確認すると以下のような感じ。

timenamelatlon
2018/10/31,20:14:00nanami35.66381009139.7022372
2018/10/31,20:14:00fushiguro35.6629513139.7026899
2018/10/31,20:14:00ino35.66294972139.701695

 

この作業だけは手作業に頼らざるを得ず、時間が正しいか、位置情報が誤っていないかを確認しながらひたすら積み上げていきます。

ひよこ
ひよこ

実際には可視化したときにキャラクターが被ってしまったり、位置関係に違和感を感じてしまったりすることが多いため少しずつ可視化をして緯度経度を調整して、、、の繰り返しになります。

b. アイコン

続いてアイコンです。foliumに呪術廻戦のキャラクターアイコンが入っているわけがありませんから、自分で用意する必要があります。

今回は実際の漫画やアニメからキャラクターのイメージを拝借しました。

例:虎杖悠仁くん(呪術廻戦より)

キャラクター分イメージを用意し、地図にマッピングするために背景を透明化すれば準備OKです。後にプログラミングすることを考慮し、ファイル名をキャラクター名と一致させておきました(例:itadori.png)。

【Python】foliumのマーカーアイコンを自由に変更する方法
foliumを使って位置情報の可視化を体験できた!地図上にマーカーをプロットできるけど、アイコンを自由に変更することはできないかな。。こんな悩みにお答えします。実は、foliumでは簡単にアイコンを変更することができます。今回は、foliu

 

4. 処理の流れ

ここから、具体的な処理の流れをご説明します。主に以下の記事を参考に処理を進めていきます。

・特定時間帯の位置情報を可視化
・すべての時間帯のpngファイルを作成
・pngファイルを繋ぎ合わせてGIF作成

a. 特定時間帯の位置情報を可視化

今回は時間帯を指定し可視化することを検討しました。理由は以下のとおり。

  • キャラクターによって正確な位置と時間が把握できないことが多いから
  • 細かい粒度(1分ごとなど)で可視化すると、動きの少ないファイルが大量に生成されてしまうから

 

実際に位置情報を収集する中で、5分と10分を使い分けて可視化することとしました。具体的な時間帯指定はこちらです。

  • 20:00~20:30(10分ごと)
  • 20:30~20:40(5分ごと)
  • 20:40~23:00(10分ごと)
  • 23:00~23:20(5分ごと)
  • 23:20~23:50(10分ごと)

 

各時間帯の可視化にはfoliumのMarker()メソッドを使用します。

 

b. すべての時間帯のpngファイルを作成

各時間帯を一つずつ、すべての時間帯の可視化を行います(20:00~20:10の可視化、20:10~20:20の可視化、、、)。

foliumで作成されたファイルはhtmlファイルであるため、それらのスクリーンショットを取りpngファイルを作成していきます。

ここではseleniumライブラリのwebdriverを使用します。

 

c. pngファイルを繋ぎ合わせてGIF作成

最後に、作成されたpngファイルをすべて繋ぎ合わせることでGIFを作成します。

PILを用いて複数のpngファイルからGIFを作成しています。

 

5. サンプルコード

処理ごとに記述していきます。

a. 特定時間帯の位置情報を可視化

import folium
from folium import plugins
from datetime import timedelta
import datetime
from folium.features import CustomIcon
from IPython.display import Image
import pandas as pd

#ベースとなるマップを作成する関数
def base_map():
   map = folium.Map(
      [35.66256010486336, 139.7016042283173],
      tiles="Stamen Terrain",
      zoom_start=16
   )
   return map


#帳と伏魔御厨子の範囲を表示する関数
def tobari(map,start,end):
   #帳(一般人閉じ込める)
   if start >= datetime.datetime(2018, 10, 31, 19, 0, 0, 0):
      folium.Circle(location = [35.659034, 139.701636],radius = 400,color = "red",fill = True ).add_to(map)
   #帳(術師入れない)
   if start >= datetime.datetime(2018, 10, 31, 20, 35, 0, 0) and end <= datetime.datetime(2018, 10, 31, 22, 10, 0, 0):
      folium.Circle(location = [35.659034, 139.701636],radius = 350,color = "blue",fill = True ).add_to(map)
   #帳(五条さん閉じ込める)
   if start >= datetime.datetime(2018, 10, 31, 20, 40, 0, 0):
      folium.Circle(location = [35.65961246, 139.7036947],radius = 90,color = "yellow",fill = True ).add_to(map)
   #帳(一般人閉じ込める)
   folium.Circle(location = [35.65961246, 139.7036947],radius = 60,color = "red",fill = True ).add_to(map)
   #伏魔御厨子
   if start >= datetime.datetime(2018, 10, 31, 23, 5, 0, 0) and end <= datetime.datetime(2018, 10, 31, 23, 10, 0, 0):
      folium.Circle(location = [35.66109599528823, 139.6987660100766],radius = 140,color = "black",fill = True ).add_to(map)


#アイコンを作成する関数
def get_image(img_name='default.png'):
   icon = CustomIcon(
      icon_image = img_name,
      icon_size = (55, 65),
      icon_anchor = (30, 30),
      popup_anchor = (3, 3)
   )
   return icon


#マップを保存する関数
def save_map(map,map_file="default"):
   map_file = map_file + ".html"
   map.save(map_file)

#アイコンを表示する関数
def character_view(map,img_name,icon_latlon,popup):
   icon = get_image(img_name)
   folium.Marker(location = icon_latlon,icon = icon,popup=popup).add_to(map)

#特定時間帯のマップを作成し保存する関数
def specific_map_view(data,map,start,end,span):
   forcus_time_st = start
   forcus_time_ed = start + span
   k = 1
   while True:
      forcus_data = data[data['time'] < forcus_time_ed]
      forcus_data = forcus_data[forcus_data['time'] >= forcus_time_st]
      tobari(map,forcus_time_st,forcus_time_ed)
      for i in range(len(forcus_data)):
         img_name = "./pic/" + str(forcus_data[i:i+1]["name"].values[0]) + ".png"
         icon_latlon = forcus_data[i:i+1][["lat_2","lon_2"]].values.reshape(-1).tolist()
         character_view(map,img_name,icon_latlon,forcus_data[i:i+1]["name"].values[0]) 

      if forcus_time_st.minute <=9:
         save_map(map,map_file="./pic/jujutu/default_"+str(forcus_time_st.hour)+"_0"+str(forcus_time_st.minute))
      else:
         save_map(map,map_file="./pic/jujutu/default_"+str(forcus_time_st.hour)+"_"+str(forcus_time_st.minute)) 

   forcus_time_st = forcus_time_ed
   forcus_time_ed = forcus_time_ed + span
   map = base_map()
   k += 1

   if forcus_time_ed > end:
      break


if __name__ == '__main__':
   try:
      latlon = pd.read_excel(
         './jujutu/jujutu.xlsx',
         header=0,
         index_col=None,
         engine='openpyxl'
      ) #engine='openpyxl'がないとエラーが出る
 latlon['time'] = pd.to_datetime(latlon['time'], format="%Y/%m/%d,%H:%M:%S") map = base_map() start = datetime.datetime(2018, 10, 31, 20, 0, 0, 0) #開始時間を指定する必要あり end = datetime.datetime(2018, 10, 31, 20, 10, 0, 0) #終了時間を指定する必要あり span = datetime.timedelta(minutes=10) #時刻の間隔を指定する必要あり specific_map_view(latlon,map,start,end,span) except Exception as e: print(e)

 

b. すべての時間帯のpngファイルを作成

import os
import glob
from selenium import webdriver
from datetime import date
import datetime
import time


save_img_path = "./pic/jujutu_png/"

files = glob.glob("./pic/jujutu/*") 
driver_path = "./chromedriver.exe"
delay=5

#一つずつhtmlファイルのスクリーンショットを取る
for file in files:
   file = file[13:] #./pic/jujutu/部分を削除
   tmpurl='file://{path}/pic/jujutu/{mapfile}'.format(path=os.getcwd(),mapfile=file)

   driver = webdriver.Chrome(driver_path)
   driver.get(tmpurl)
   time.sleep(delay)


   save = str(save_img_path) + str(file[:-5]) + ".png" #スクリーンショットをpngファイルとして保存
   driver.save_screenshot(save)
   driver.quit()

 

c. pngファイルを繋ぎ合わせてGIF作成

from PIL import Image, ImageDraw, ImageFont
from datetime import datetime, date, timedelta
import glob
import pandas as pd


im = []
files = glob.glob("./pic/jujutu_png/*")

latlon = pd.read_excel('./jujutu/jujutu.xlsx', header=0, index_col=None,engine='openpyxl')
latlon['time'] = pd.to_datetime(latlon['time'], format="%Y/%m/%d,%H:%M:%S")

year_text = latlon['time'][0].year
month_text = latlon['time'][0].month
day_text = latlon['time'][0].day

#pngファイルを繋ぎ合わせてGIFを作成
for i in range(len(files)):
   img = Image.open(files[i])
   draw = ImageDraw.Draw(img)
   font = ImageFont.truetype("FontsFree-Net-arial-bold.ttf", 70)
   filename = files[i][17:]
   text = str(year_text)+"/"+str(month_text)+"/"+str(day_text)+" "+str(filename[8:10])+":"+str(filename[11:13])
   draw.text((30, 30),str(text),font=font,fill=(0,0,0))
   im.append(img)

im[0].save(
   './pic/jujutu_png/jujutu.gif',
   save_all=True,
   append_images=im[1:],
   optimize=False,
   duration=1500,
   loop=0
)

 

d. 作成されたGIF

改めて、こちらが作成されたGIFです。

帳(赤や青の円)が確認でき、キャラクターが移動していることも分かります。また、上部に時刻を記載することで時間推移が分かりやすくなっています。

 

6. まとめ

今回は、呪術廻戦の渋谷事変を参考にfoliumで時系列位置情報を可視化する方法をご紹介しました。

foliumの使い方、その具体例としては最適ではないでしょうか。あなたもfoliumを使って色々なものを可視化してみましょう。

このとき大切なことは、「何ができるか」ではなく「何をしたいか」です。できることから探さずに、やりたいことをやるためにはどうすればいいかを考えていきましょう!

Pythonで人生を豊かにしていきましょう。

ひよこ
ひよこ

わたし個人としては、データ収集(作成)の難しさを痛感する機会になりました。。。

 

 

最後に、今回参考にさせていただいた資料です。

>>呪いを祓え!「呪術廻戦」とたまごっちがコラボした「じゅじゅつっち」登場
>>アニメ映画『劇場版 呪術廻戦 0』本年度 No.1 超大ヒットスタート&満足度、驚異の98%超えを記録! 七海・冥冥・京都校生らの出演情報が解禁! 日下部篤也役として声優・三木眞一郎さんが出演
>>AnimePress呪術廻戦
>>pixiv百科事辞典陀艮
>>組屋鞣造(呪術廻戦)の徹底解説・考察まとめ
>>呪術廻戦の名前や年齢や身長等、キャラのプロフィールをご紹介
>>pixiv百科事典伏黒甚爾
>>呪術廻戦の裏梅は誰?正体は何者で宿儺や五条家関連の一族か考察!何巻何話初登場かも徹底調査
>>pixiv百科事典九十九由基
>>pixiv百科事典八握剣異戒神将魔虚羅

 

 

本ブログでは、株式やプログラミングに関する記事を投稿しています。

プログラミング(Python)を学びたい方におすすめの書籍やプログラミングスクール、おすすめの学習方法などをご紹介しておりますのでぜひご覧ください。

独学で挫折してしまう方は、プログラミングスクールという方法がおすすめです。

私の一押しは『TechAcademy』です。

質問することですぐに分からないところをクリアにできますし、進捗をサポートしてくれるため確実に成長することができます。

無料相談を実施しているため、まずは話を聞いてあなたのスタイルに合っているかどうか確認してみるのが良いと思います。

スキルアップを目指したい方におすすめのオンライン学習サービス

 

コメント

タイトルとURLをコピーしました