【Python】写真の位置情報のタグ(JPGファイルのGPS Exif)の読み書き

# はじめに
これも自分用のメモです。python を使うと楽にできます。注意点として、

- 緯度経度は度分秒なので６０進数
- 値は実数ではなく、分数で。（有理数、というのかな）

いくつか関係するライブラリがあります。どれか一つに統一したほうがよいのですが、とりあえず動いたものメモです。（おい）

### 仕様書?

下記の文献を見つけました。

- [デジタルスチルカメラ用 画像ファイルフォーマット規格 Exif 2.3](http://www.cipa.jp/std/documents/j/DC-008-2012_J.pdf)

これによるとGPS Info IFDは以下のように定義されているようです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/294040/848f1cb4-5182-9957-ed1c-ab45027c2d19.png)

たくさんありますが、私が扱ったファイルでは0 - 6 までのタグのみでした。GPSの緯度、経度、高さが読み書きできたので、とりあえずは十分でした。ここでGPSLatitudeRefは一文字の'N'か'S'で北緯、南緯を表します。GPSLatitudeは分数で表します。

# python で読み書きする

### Pillow で緯度経度を読む

[Pillow](9https://pypi.org/project/Pillow/) を使用しました。準備で苦労したかは覚えていません。

```
pip3 install pillow
```

下記で緯度経度を読めました。

```python:read_gpsexif.py
from PIL import Image
infile = "test.JPG"
img = Image.open( infile )
exif = img._getexif()
for k, v in exif.items():
    if k==34853:
        print(v)
```

これで

```python
{0: b'\x02\x00\x00\x00',
 1: 'N',
 2: (35.0, 20.0, 53.51123),
 3: 'E',
 4: (137.0, 9.0, 17.83123),
 5: b'\x00', 6: 256.123}
```

のような結果を得ます。先の仕様書のGPS INfo IFD の定義と見比べると意味が分かると思います。度分秒なので、１０進法にするには変換が必要です。例えば

```python

deg,minu,sec = 35.0, 20.0, 53.51366
deg + minu/60.0 + sec/3600.0
```

で ```35.34819823888889``` を得ます。


## piexif でGPS Exif の緯度経度を書き換える

[piexif](https://pypi.org/project/piexif/)を利用しました。
度分秒と分数への変換を関数にし、Gps exif を読んで書き換える作戦です。```Fraction```は実数を有理数に変換してくれる関数です。文字列だと```3/4```になるので、分子と分母をset にして返す処理を関数にしています。

```python
def _to_deg(value:float, loc:list):
    """
    Convert degree in decimal to degree, minutes, and second. Note Degree and minute is integer and seconds is float.
    """
    if value < 0:
       loc_value = loc[0] # 'S' or 'E' 
    elif value > 0:
       loc_value = loc[1] # 'N' or 'W'
    else:
       loc_value = ""
    abs_value = abs(value)
    deg =  int(abs_value) # integer part
    t1 = (abs_value-deg)*60
    minu = int(t1) # minutes
    sec = round((t1 - minu)* 60, 5) # seconds
    return (deg, min, sec, loc_value)

from fractions import Fraction
def _change_to_rational(number):
    f = Fraction(str(number))
    return (f.numerator, f.denominator)
```

を用いて、以下を実行します。

```python:write_gpsexif.py
import piexif
lat, lng, altitude = 39.123, 139.123, 50.123
jpg_filepath = "test.JPG"

# convert decimal to degree, minutes, seconds
lat_deg = to_deg(lat, ["S", "N"])　
lng_deg = to_deg(lng, ["W", "E"])
exif_lat = (change_to_rational(lat_deg[0]), 　change_to_rational(lat_deg[1]), change_to_rational(lat_deg[2]))
exif_lng = (change_to_rational(lng_deg[0]), change_to_rational(lng_deg[1]), change_to_rational(lng_deg[2]))
gps_ifd = {
    piexif.GPSIFD.GPSVersionID: (2, 0, 0, 0),
    piexif.GPSIFD.GPSAltitudeRef: 0,
    piexif.GPSIFD.GPSAltitude: change_to_rational(round(altitude, 3)),
    piexif.GPSIFD.GPSLatitudeRef: lat_deg[3],
    piexif.GPSIFD.GPSLatitude: exif_lat,
    piexif.GPSIFD.GPSLongitudeRef: lng_deg[3],
    piexif.GPSIFD.GPSLongitude: exif_lng,
}

gps_exif = {"GPS": gps_ifd}
exif_data = piexif.load(jpg_filepath)
exif_data.update(gps_exif)
exif_bytes = piexif.dump(exif_data)
piexif.insert(exif_bytes, file_name)
```

これは確かStackOverflow を参考にさせていただいたと思うのですが、linkを無くしてしまいました。見つけたら追加します。

## まとめ・雑感

必要にせまらて写真のGPSExif の読み書きをした時のメモです。

- Pillow, piexif を利用したが、何となくどちらかに統一して正しく使うのがよさそう。

えいやの作業メモなので、拡充させていきたい、、、という投稿でした。最近雑だな。。^^; (2020/08/08)


コードのタイポを修正しました。