見出し画像

Python - 写真から位置情報を読み取って csv ファイルとして保存する

概要

写真から緯度経度の情報を取り出して、csv ファイルにする Python Script を作りました。

地名と緯度経度の情報を別ファイルで用意すれば、一番近い場所の地名を返します。

準備

1.Pillow をインストールします。

sudo pip3 install pillow で行けるでしょうか。

この意味がわからない人は下の方を読んでください。

2.Address_List.csv ファイルを用意します。

場所と緯度経度の対応表です。

ファイルの仕様

1行目に、Location Name および Lat and Lon と記入してください。これがないとエラーになります。画像は、LibreOffice Version 7.0.0.3 の画面です。

画像2

A列の Location Name として、コードではなく地名を書き込めば応用範囲が広がると思います。

csv 形式で保存します。設定は↓

画像1

このファイルがなくても動作します。ない場合には、ファイル名と緯度経度、サイズだけの csv ファイルが出来上がります。

3.Script ファイルを用意します。

下のコードをテキストファイルで保存。拡張子は py です。
それでうまくいくかはわかりません。

私の場合は、アプリケーションフォルダー > Python 3.8 フォルダー > IDLE を起動。メニューから New File を実行し、開いたウィンドウに貼り付けます。

※ 最初に開いたウィンドウに貼り付けるのではなく、New File で開いたウィンドウに貼り付けてから保存、です。

4.次のようにファイルを配置します。

あるフォルダー
        ┣ このスクリプト
        ┣ Address_List.csv
        ┣ jpeg ファイル1
        ┣ jpeg ファイル2
        ┣ .......
        ┣ .......
        ┣ .......
     ┣ jpeg ファイルX
        ┗ Picture_Address.csv(Script が生成するファイル)

GPS データを含まない写真の場合は、ファイル名とサイズだけのリストができあがります。サイズの表記は (1280, 960) です。何かに使うと思って用意しましたが、結局使っていません。

*.jpg 限定で処理しています。
*.jpeg ではだめです。ファイル形式を増やす場合は、Script を修正してください。

手順

Script をダブルクリックして実行します。

Picture_Address.csv というファイルができあがります。

コード

いろいろなところからもらってきて作りあげた継ぎ接ぎだらのコードです。正直、よく理解していません。変数名をのつけ方とか癖のある書き方になっていると思いますが、ご容赦いただければと思います。

# -*- coding: utf-8 -*-

import os
import glob
from PIL import Image
import PIL.ExifTags as ExifTags
import csv
from math import sin, cos, radians, sqrt, asin

def get_gps(fname, dic_address):

   MyLocation = ''
   Address_Code = ''
   Distance = ''       
#
# 画像ファイルを開く
#
   im = Image.open(fname)
   exif = im._getexif()

#
# EXIF情報をゲット
# 辞書に格納する
#
   exif = {
       ExifTags.TAGS[MyKey]: MyValue
       for MyKey, MyValue in exif.items()
       if MyKey in ExifTags.TAGS
   }            
           
#    print("GPSInfo" in exif, ':', os.path.basename(file))

#
# EXIF に GPSInfo タグが含まれているときに処理
#
   if 'GPSInfo' in exif:
#
# GPS情報を得る
#
       gps_tags = exif['GPSInfo']
#
# GPS情報をゲット
# 辞書に格納する
#   
       gps = {
           ExifTags.GPSTAGS.get(t, t): gps_tags[t]
           for t in gps_tags
       }

# タグがあるかどうか
       is_lat = 'GPSLatitude' in gps
       is_lat_ref = 'GPSLatitudeRef' in gps
       is_lon = 'GPSLongitude' in gps
       is_lon_ref = 'GPSLongitudeRef' in gps

       if is_lat and is_lat_ref and is_lon and is_lon_ref:
   
# 緯度経度情報を得る
# 度分秒 > 十進経緯度
#
# 緯度
           lat = gps['GPSLatitude']
           lat_ref = gps['GPSLatitudeRef']
           # Plus or Minus Sign for lat_Dec
           if lat_ref == 'N':
               lat_sign = 1.0
           elif lat_ref == 'S':
               lat_sign = -1.0
# 経度
           lon = gps['GPSLongitude']
           lon_ref = gps['GPSLongitudeRef']
           # Plus or Minus Sign for lon_Dac
           if lon_ref == 'E':
               lon_sign = 1.0
           elif lon_ref == 'W':
               lon_sign = -1.0

           lat_ang0 = lat_sign * lat[0] + lat[1] / 60 + lat[2] / 3600
           lon_ang0 = lon_sign * lon[0] + lon[1] / 60 + lon[2] / 3600
# 角度をラジアンに変換
# Haversine formula では、それぞれφ、λと表記している。
           phi0 = radians(lat_ang0)
           lam0 = radians(lon_ang0)

           EARTH_RADIUS_m = 6378137
           radius = EARTH_RADIUS_m

# dic_address: 既知の地番と緯度経度の対応リスト
# データがあるときだけ処理
           if dic_address:

               MyDic = {}
                       
               for k in dic_address:
                   
                   ref_address = k
                   ref_location = dic_address[k]
           
                   if ',' in ref_location:

                       lat_ang1 = float(ref_location.split(',')[0])
                       lon_ang1 = float(ref_location.split(',')[1])
# 角度をラジアンに変換
                       phi1 = radians(lat_ang1)
                       lam1 = radians(lon_ang1)
#  Haversine formula を使い距離を計算
                       term0 = sin((phi1 - phi0) / 2.0) ** 2
                       term1 = sin((lam1 - lam0) / 2.0) ** 2
                       term1 = cos(phi0) * cos(phi1) * term1
                       wrk = sqrt(term0 + term1)
                       d = 2.0 * radius * asin(wrk)
    #       print(row[0], wrk)

                       MyDic[ref_address] = d

# 最も近い番地とそこまでの距離を求める
               min_Key, min_Value = min(MyDic.items(), key=lambda x: x[1])
#
#            print(u'最短は',min_Key, min_Value, 'm')
#       
               Address_Code = min_Key
               Distance = str(min_Value)

           MyLocation = str(lat_ang0) + ', ' + str(lon_ang0)

       MySize = im.size
           
   return [os.path.basename(file), MyLocation, Address_Code, Distance, MySize]
#
if __name__ == '__main__':

# アドレスリストを辞書型で読み込む
# 同じフォルダーにある Address.csv
# ファイルがなかったら辞書は空に
   Address_File = './Address_List.csv'
   if os.path.exists(Address_File):
       with open(Address_File, 'rt') as csvfile:
           reader = csv.DictReader(csvfile)
           dic_address = {}
           for row in reader:
               dic_address[row['Location Name']] = row[u'Lat and Lon']
   else:
       dic_address = {}
       
# 結果を書き出す

   with open('./Picture_Address.csv', 'w') as f:
       writer = csv.writer(f)

# 同じフォルダーにある JPEG ファイルを処理する
       files = glob.glob('./*.jpg')
       for file in files:
# 写真のGPS情報をゲット
           loc = get_gps(file, dic_address)
# 1行書き込む
           writer.writerow(loc)
           
       print(f.read)

2020年9月6日追記
最後の行に f.close() と書いていましたが、不要だったので削除しました。

参考にしたサイト

主に次のサイトを参考にしました。

フォルダー内のファイル一覧

EXIF タグの扱い

辞書内包表記が使われていたりしますが、正直理解できていません。

緯度経度から2地点間の距離を求める

辞書の値が最大・最小となるキーと値を同時に取得

各地点間の距離を算出した後、最も近い場所を判定するために使いました。

csv ファイルを辞書型で読み込む


いろいろ検索していると、TECHACADEMY magazine が上位に出てきますが、対話形式の記事がうざいと思います。


MacOX で 初めて Python を使う人のために

Python を使うのが初めての人で、Script が走ればいいや、後のことはなんとかなるさという楽観的な人は、ここに書く手順を参考にしてやってみてください。

あとあと問題になるとしたら、元に戻すのが面倒くさそうだということくらいですかね。戻したいけど戻せない、と文句を言わないようお願いします。

まあ、解決策はネットに書いてあるものと思います。

手順

Python のインストーラーをもらってきてインストールします。次に、モジュールをインストールします。

1.Python のインストール

インストーラーをゲットします。

インストールします。

2.モジュールのインストール

ターミナルを起動します。

※ Finder のメニューバー > 移動 > ユーティリティ とやって、ターミナルという名前のアイコンをダブルクリック

モジュールをインストールするときに、上のサイトを参考にしました。

先に pip をインストールします。

※ pip さえもいらないという人は、Mac には標準で easy_install というのが装備されているようなので、それを使えば良いかと思います。

ターミナルのウィンドウに sudo easy_install pip とタイプして、エンターキーを押します。

ここでパスワードを聞いてきますかね。パスワードは、MacOS の現在のユーザーのパスワードです。

次は pillow のインストールです。

sudo pip3 install pillow とタイプして、エンターキーを押します。

インストールがうまくいったら、ターミナルは閉じて良いです。

正しい閉じ方があるのでしょうか。ありました。

3.開くアプリケーションの設定

私の環境では、Python Script の *.py というファイルには XCode が関連づけられていました。
Script ファイルを右クリック > 情報を見る > このアプリケーションで開く > Python Launcher を設定しました。

以上。

手順が抜けていたら申し訳ありません。

余談

なんでこういうのを作ったかというと、自然観察の記事を効率的に作成したかったからです。というか、面倒くさいのが嫌いなんです。

このスクリプトで得られた緯度経度は Google Map に貼り付けて使えます。最初、Google Map に緯度経度を貼り付けて位置を確認していたのですが、写真が何十枚もあるので面倒になりました。Google Map が検索するたびに縮尺を戻してくるのも面倒でした。

緯度経度から距離を算出できたら自動化できるじゃないかと。実現すれば手作業から解放されるわけです。面倒臭いことをやらなくて良くなるならば、モチベーションも上がります。

Mac でも Windows でも、Python のインストーラーをもらってきてインストールすれば、いつも使っている OS 上で Python Script を動かせるようになります。もちろん自分でもScript を作れるようになります。

しかも、Linux 領域ではなく、いつも使っている OS 上で。

本格的に Python をやる人の場合、環境構築がどうのこうのと難しいことばかり言っていますよね。正直何を言っているのかわかりません。調べれば調べるほど、Python が遠くなっていきます。ま、そんな中で、インストールしてしまえ、となったわけです。やってみたら簡単でした。

そもそも本気で Python やるなら、マシンを用意した方が良くないですか。ていうか、自分の場合、Raspberry PI だったり、Jetson Nano だったりしますけど。

Windows の場合、VBA から Script を起動して、VBA と Python の合わせ技で処理するなんてやっていました。

LibreOffice Basic から Script を起動するのは、まだやってみていません。できるかどうか調べてもいないです。

話は変わりますが、MacOS で Script をダブルクリックすると、余計なウィンドウが開くなあと不満に思っています。

Mac の場合、Python 本体はどこかに隠れていて、ファインダーから Script を起動すると、Python Launcher が隠れている人に Script を取り次いでくれるようです。というか、そんなイメージで見ています。Python Launcher も隠れていていいのですけどね。

ちなみに、OS は MacOSX El Capitan です。

Python Launcher の Preference Run in a Terminal window をオフにすれば、ターミナルウィンドウは表示されませんが、Python Launcher が起動したままになります。Script の処理が終わったら、何事もなかったかのように静けさを取り戻す、それがいいです。

t.koba


この記事が気に入ったらサポートをしてみませんか?