しばらく前にイタリアを旅行しました。ミラノ・ヴェネツィア・フィレンツェ・ピサ・ローマ・ポンペイを1週間で回るツアーで、気づいたら1000枚くらい写真を撮っていました〜。
そこそこ自由時間もあって毎日1万歩くらいは散策したのでルート込みで記録に残しておきたい、ということで地図アルバムを作ってみました……!
可視化
全体像はこんな感じ。OpenStreetMapの地図を使ってます。
ここから、たとえばヴェネツィアをズームアップすると、こんな風に散策ルートが見れます。日にちによって色分けをしていて、ヴェネツィアは一泊したので二色ルートがありますね。
そしてマーカーが写真を撮ったポイントで、クリックすると写真がポップアップします。
こちらが元写真です。ゴンドラ遊覧中に撮ったので、マーカーも水路上にあります。
ヴェネツィアは『ARIA』の聖地ということで楽しみにしていたのですが、期待より遥かに幻想的な水の都でした。全力でオススメします。
こちらは不朽の名作『ローマの休日』の聖地であるところのスペイン広場です。行きの飛行機で観ました(ぉぃ
こちらは噴火で滅んだポンペイを見守る猫です。ここにねこが出没します。ねこでした。よろしくおねがいします
反応
家族や友人たちに共有してみたのですが、旅路を追いながら写真を見せつつ思い出を話せたので、だいぶウケ良かったです。この屋台通りを抜けて、このお店でふらっと夕食取って、この道走って集合時間ギリギリだったんだよー、みたいな。
ただJupyterLabのノートブックをエクスポートしたHTMLファイルをそのまま共有したので、エンジニア系の人たちはPythonのコードを読みはじめてしまい、こちらの話をなかなか聞いてくれないという難点はありましたw
手順
JupyterLab上で50行程度のPythonコードを書いて、次のような処理を行いました。
Foliumは業務でも使いはじめているのですが、とてもお手軽ですね。
- Pillowで画像ファイルを読みこむ。
- 画像のExifから緯度・経度の情報を抜く。
- 画像のExifから回転・反転の情報を抜いて適用する。
- Foliumに緯度・経度の列を食わせてルートを描く。
- Foliumに緯度・経度とBase64にエンコードした画像を食わせてマーカーを打つ。
- JupterLabでHTML出力する。
Foliumのマーカーにはimageタグを渡せるのですが、どうもローカルファイルは参照できないようなので、Base64にエンコードするという荒技を使っています。
おかげで独立した単一のHTMLファイルを出力できるので共有するのは楽なのですが、縮小しているとはいえ1000枚ほどの画像をぶち込んでいるので、100MBほどのHTMLファイルになりました……w
コード
import base64
import folium
import glob
import pandas as pd
from io import BytesIO
from matplotlib import pyplot as plt
from PIL import ExifTags, Image, ImageOps
def to_deg(v, ref, pos):
d = float(v[0][0]) / float(v[0][1])
m = float(v[1][0]) / float(v[1][1])
s = float(v[2][0]) / float(v[2][1])
return (d + (m / 60.0) + (s / 3600.0)) * (1 if ref == pos else -1)
to_trans_methods = {
1: [],
2: [Image.FLIP_LEFT_RIGHT],
3: [Image.ROTATE_180],
4: [Image.FLIP_TOP_BOTTOM],
5: [Image.FLIP_LEFT_RIGHT, Image.ROTATE_90],
6: [Image.ROTATE_270],
7: [Image.FLIP_LEFT_RIGHT, Image.ROTATE_270],
8: [Image.ROTATE_90]
}
files = glob.glob('/path/to/*.jpg')
rows = []
for file in files:
with Image.open(file) as im:
exif = {ExifTags.TAGS[k]: v for k, v in im.getexif().items() if k in ExifTags.TAGS}
if 'GPSInfo' in exif:
gps = {ExifTags.GPSTAGS[k]: v for k, v in exif['GPSInfo'].items() if k in ExifTags.GPSTAGS}
lat = to_deg(gps['GPSLatitude'], gps['GPSLatitudeRef'], 'N')
lon = to_deg(gps['GPSLongitude'], gps['GPSLongitudeRef'], 'E')
im.thumbnail((192, 192))
for method in to_trans_methods[exif.get('Orientation', 1)]:
im = im.transpose(method)
buf = BytesIO()
im.save(buf, format="png")
rows.append([lat, lon, exif['DateTimeOriginal'], base64.b64encode(buf.getvalue()).decode()])
df = pd.DataFrame(rows, columns=['lat', 'lon', 'dt', 'base64'])
df['dt'] = pd.to_datetime(df['dt'], format='%Y:%m:%d %H:%M:%S')
df = df.sort_values('dt')
fmap = folium.Map(location=[df['lat'].mean(), df['lon'].mean()], zoom_start=6)
hsv=[plt.get_cmap('hsv', 12)(i) for i in range(12)]
fmap.add_child(folium.ColorLine(zip(df['lat'], df['lon']), colors=df['dt'].dt.day, colormap=hsv, weight=4))
for _, row in df.iterrows():
fmap.add_child(folium.Marker([row['lat'], row['lon']], popup=f'<img src="data:image/png;base64,{row["base64"]}">'))
fmap
実行環境
$ python --version
Python 3.7.4
$ pip list | grep -e folium -e jupyter -e matplotlib -e pandas -e Pillow
folium 0.10.1
jupyter-client 5.3.3
jupyter-core 4.5.0
jupyterlab 1.1.4
jupyterlab-server 1.0.6
matplotlib 3.1.2
pandas 1.0.1
Pillow 7.0.0
参考リンク
Pythonで写真に埋め込まれているGPS情報から撮影場所を調べよう | マイナビニュース
PILでEXIF Orientationタグを考慮して処理 | Qiita
View image on popup | python-visualization/folium