Androidアプリで撮影した写真(画像ファイル)に位置情報や撮影日時を付与しようとしたときにいろいろ苦戦したのでそのときのメモです。画像ファイルのExif属性に位置情報や撮影日時を書き込む方法とフォーマットについて説明しようと思います。

Exifを書き込む際の基本的な流れ

基本的にはAndroidのExifInterfaceというクラスを使うと簡単に書き込むことができます。

// Exifを追加する対象の画像ファイルを指定
ExifInterface ex = new ExifInterface("/xxx/xx/xxx/image.jpg");
// exifを必要分セットする
ex.setAttribute(ExifInterface.TAG_GPS_LATITUDE,"35/1,37/1,5538936/100000");
...
// 保存する
ex.saveAttributes();

Exifに書き込む情報はフォーマットが規格化されている

じゃあ何に苦戦したかというと、Exifに書き込む内容はフォーマットが規格化されており、そのフォーマットに合わせる必要があるということです。例えばGPSの緯度だったら「35/1,37/1,5538936/100000」のような度分秒(DMS)フォーマットで保存する必要があります。

現在はExifのバージョン2.31が最新ですが、下記サイトにフォーマット規格が詳しく書いてあります。

http://www.jeita.or.jp/cgi-bin/standard/list.cgi?cateid=1&subcateid=4

位置情報をExifに書き込む具体的なサンプル

位置情報(緯度、経度)をExifに書き込むには下記のように4つのタグを書き込みます。Androidで取得できる位置情報は「139.1234566」のような形式なのでこれを下記のような度分秒の文字列に変換する必要があります。

// 位置情報をExifにセットするサンプル
ex.setAttribute(ExifInterface.TAG_GPS_LATITUDE, "35/1,36/1,1538936/100000"); //緯度
ex.setAttribute(ExifInterface.TAG_GPS_LATITUDE_REF, "N"); //緯度の符号(北か南か)
ex.setAttribute(ExifInterface.TAG_GPS_LONGITUDE, "139/1,34/1,2885044/100000"); //経度
ex.setAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF, "E"); //経度の符号(東系か西系か)

フォーマットを変換する関数も作ってみましたので参考までに掲載します。

public class ExifLocation {
  private String longitudeRef;
  private String longitude;
  private String latitudeRef;
  private String latitude;

  //Getter, Setterは省略
  ...
}

/**
 * Exif形式にGPS Locationを変換して返す。
 * longitudeRef => W or E
 * latitudeRef => N or S
 * latitude and longitude => num1/denom1,num2/denom2,num3/denom3
 * ex) 12/1,34/1,56789/1000
 *
 * @param location
 * @return ExifLocation
 */
public static ExifLocation encodeGpsToExifFormat(Location location) {
  ExifLocation exifLocation = new ExifLocation();
  // 経度の変換(正->東, 負->西)
  // convertの出力サンプル => 73:9:57.03876
  String[] lonDMS = Location.convert(location.getLongitude(), Location.FORMAT_SECONDS).split(":");
  StringBuilder lon = new StringBuilder();
  // 経度の正負でREFの値を設定(経度からは符号を取り除く)
  if (lonDMS[0].contains("-")) {
    exifLocation.setLongitudeRef("W");
  } else {
    exifLocation.setLongitudeRef("E");
  }
  lon.append(lonDMS[0].replace("-", ""));
  lon.append("/1,");
  lon.append(lonDMS[1]);
  lon.append("/1,");
  // 秒は小数の桁を数えて精度を求める
  int index = lonDMS[2].indexOf('.');
  if (index == -1) {
    lon.append(lonDMS[2]);
    lon.append("/1");
  } else {
    int digit = lonDMS[2].substring(index + 1).length();
    int second = (int) (Double.parseDouble(lonDMS[2]) * Math.pow(10, digit));
    lon.append(String.valueOf(second));
    lon.append("/1");
    for (int i = 0; i < digit; i++) {
      lon.append("0");
    }
  }
  exifLocation.setLongitude(lon.toString());
 
  // 緯度の変換(正->北, 負->南)
  // convertの出力サンプル => 73:9:57.03876
  String[] latDMS = Location.convert(location.getLatitude(), Location.FORMAT_SECONDS).split(":");
  StringBuilder lat = new StringBuilder();
  // 経度の正負でREFの値を設定(経度からは符号を取り除く)
  if (latDMS[0].contains("-")) {
    exifLocation.setLatitudeRef("S");
  } else {
    exifLocation.setLatitudeRef("N");
  }
  lat.append(latDMS[0].replace("-", ""));
  lat.append("/1,");
  lat.append(latDMS[1]);
  lat.append("/1,");
  // 秒は小数の桁を数えて精度を求める
  index = latDMS[2].indexOf('.');
  if (index == -1) {
    lat.append(latDMS[2]);
    lat.append("/1");
  } else {
    int digit = latDMS[2].substring(index + 1).length();
    int second = (int) (Double.parseDouble(latDMS[2]) * Math.pow(10, digit));
    lat.append(String.valueOf(second));
    lat.append("/1");
    for (int i = 0; i < digit; i++) {
      lat.append("0");
    }
  }
  exifLocation.setLatitude(lat.toString());

  return exifLocation;
}

日付情報をExifに格納するサンプル

日付のフォーマットは「yyyy:MM:dd HH:mm:ss」になります。日付関係のタグはいくつかあるためそれぞれについて少し補足します。

  • TAG_DATETIME
    ・・・ファイル変更日時
  • TAG_DATETIME_DIGITIZED
    ・・・画像がデジタルデータ化された日時(API24以上)
  • TAG_DATETIME_ORIGINAL
    ・・・現画像データ生成日時(API24以上)
  • TAG_GPS_DATESTAMP
    ・・・UTCに基づく日付情報。フォーマットは「yyyy:MM:dd」

撮影日時は「TAG_DATETIME_ORIGINAL」にあたるかと思いますが、APIレベル24以上でないとサポートされていないので注意が必要です(書き込めるが不明なタグとして認識されてしまう)。APIレベル23以下もサポートする場合は「TAG_DATETIME」を使うのが無難かもしれません。