search
LoginSignup
10
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

updated at

イタリア旅行の地図アルバムをPythonで作って共有してみた結果

しばらく前にイタリアを旅行しました。ミラノ・ヴェネツィア・フィレンツェ・ピサ・ローマ・ポンペイを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

Register as a new user and use Qiita more conveniently

  1. You can follow users and tags
  2. you can stock useful information
  3. You can make editorial suggestions for articles
What you can do with signing up
10
Help us understand the problem. What are the problem?