画像の位置情報

EZwebのGPS対応端末では画像(jpeg)や動画にGPSの位置情報を含めることができます。jpegではExif位置情報として設定されます。この情報を利用すると、最近良く見かけるBlog投稿画像の位置情報表示サービス等を実現可能になります。

ImageIOのメタデータ

ImageIOの標準JpegReaderではExifのサポートが不充分です。既にRFEが出ています(随分昔ですが)ので、将来的に実装される可能性がありますが、現時点では他の方法を使う必要があります。

ImageIOで取得されるExif

標準のImageIOでメタデータを取得すると、Exifは "unknown"タグとして取得可能になります。(MarkerTag属性が225)

このノードのgetUserObjectを呼び出すことにより、Exifのバイナリデータ(byte[])を取得することができます。

Exifの解析

getUserObjectで得られたバイナリデータを解析することで位置情報を取得可能になりますが、解析処理を一から実装するのは大変です。今回はhttp://www.drewnoakes.com/code/exif/のライブラリを利用させていただいて実現します。

仮に一から実装する場合は、http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/gps.htmlの情報がとても参考になります。

サンプルコード

以下は 緯度、経度を取得するサンプルです。一部エラー処理などを省略しています。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import java.util.Iterator;
import java.io.InputStream;
import java.io.FileInputStream;

import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataNode;

import org.w3c.dom.NodeList;

import com.drew.lang.Rational;
import com.drew.metadata.Metadata;
import com.drew.metadata.MetadataException;
import com.drew.metadata.exif.ExifReader;
import com.drew.metadata.exif.GpsDirectory;

public class ExifTest{

    public static void main(String[] args) throws Exception{

        InputStream is = new FileInputStream(args[0]);

        Iterator it = ImageIO.getImageReadersByFormatName("jpeg");
        ImageReader ir = (ImageReader)it.next();
        ir.setInput(ImageIO.createImageInputStream(is), truefalse);
        IIOMetadata meta = ir.getImageMetadata(0);
        double[] gps = getGpsInfo(meta);
        if (gps != null){
            System.out.println(
                "latitude=" + gps[0] + " longitude=" + gps[1]);
        }
    }

    public static double[] getGpsInfo(IIOMetadata meta){

        IIOMetadataNode root = (IIOMetadataNode)meta.getAsTree(
                meta.getNativeMetadataFormatName());

        try{
            NodeList nl = root.getElementsByTagName("unknown");
            int nodes = nl.getLength();
            for (int i=0; i < nodes; i++){
                IIOMetadataNode node = (IIOMetadataNode)nl.item(i);
                String val = node.getAttribute("MarkerTag");
                if (val != null && val.equals("225")){
                    return getGpsInfo((byte[])node.getUserObject());
                }
            }
        }
        catch(MetadataException me){
            me.printStackTrace();
        }
        return null;
    }

    public static double[] getGpsInfo(byte[] bytes)
                                throws MetadataException{

        Metadata meta = new Metadata();
        new ExifReader(bytes).extract(meta);

        GpsDirectory dir = (GpsDirectory)
            meta.getDirectory(GpsDirectory.class);
        if (dir == null){
            return null;
        }

        double lat = 0;
        double lon = 0;
        Rational[] rs;
        String s;

        if (dir.containsTag(dir.TAG_GPS_LATITUDE)){
            rs = dir.getRationalArray(dir.TAG_GPS_LATITUDE);
            lat = rs[0].doubleValue() +
                            (rs[1].doubleValue() / 60) +
                            (rs[2].doubleValue() / 3600);
        }
        if (dir.containsTag(dir.TAG_GPS_LONGITUDE)){
            rs = dir.getRationalArray(dir.TAG_GPS_LONGITUDE);
            lon = rs[0].doubleValue() +
                            (rs[1].doubleValue() / 60) +
                            (rs[2].doubleValue() / 3600);
        }
        if (dir.containsTag(dir.TAG_GPS_LATITUDE_REF)){
            s = dir.getString(dir.TAG_GPS_LATITUDE_REF);
            if (s.equals("S")){
                lat *= -1;
            }
        }
        if (dir.containsTag(dir.TAG_GPS_LONGITUDE_REF)){
            s = dir.getString(dir.TAG_GPS_LONGITUDE_REF);
            if (s.equals("W")){
                lon *= -1;
            }
        }
        return new double[]{ lat, lon };
    }
}

その他

今回利用させていただいたライブラリでは、ImageIOを介さずに直接イメージファイルからメタデータを抽出することも可能です。他のImageIOを使った操作を行わない場合、直接イメージファイルを操作する方がコードが単純になります。

また、このサンプルでは datum を無視していますが、KDDI au: 技術情報によると、仮に Tokyo だとしても WGS-84として扱う必要があるようです。これは「簡易位置情報」の記述で画像のGPS情報ではありませんが、同じと考えて良いと思います。

Exifにはその他にも撮影日時や機種情報など、利用価値のある情報が多くあります。ただ、現状では位置情報に関してはEZwebのみのようです。

EZweb動画の位置情報は動画の位置情報をご覧ください。