kintoneに登録された写真のEXIF情報から地図表示 ~ 最近彼女ができたから ~

# 妄想
彼女が出来たのでいっぱい写真撮るんです。こんな感じの写真。
![IMG_1762.JPG](https://qiita-image-store.s3.amazonaws.com/0/102790/771155c2-c990-b57f-9775-b7b17ec8057d.jpeg)


でも後で見返すとどこで撮った写真かわからないんですよね。
iPhoneだと住所は表示されるけど、地図で見たい！
彼女との想い出はできるだけスマートに保存しておきたいじゃないですか？

# 現実
仕事で現場の写真をよく撮るんです。
社内で[kintone](https://kintone.cybozu.com/jp/)を使い始めたこともあって、撮った写真をkintoneに添付するんですよ。
スマホで撮った写真を添付するの楽だし、社内のグループメンバーにもその場で共有できるし。

でも、枚数が多いから後で見るとどこで撮った写真か分からなくなるんですよね・・・
メモするのも面倒だし・・・

# やったこと
ということで、kintoneに添付した写真のEXIF情報から地図表示するサンプルコードを作りました。
Googleマップは商業利用の場合、[問い合わせが必要](https://www.google.co.jp/intl/ja/permissions/geoguidelines.html)なので今回は[OpenStreetMap](https://openstreetmap.jp/)を利用します。

# 前提
- kintoneに添付されるファイルは1個かつ画像
- 画像は縮小版ではないこと（縮小版だとEXIF情報がカットされます）
- kintoneのフォーム設定は以下の通り

| フィールド名 | フィールドタイプ | フィールドコード |
|----------|----------------|------------------|
| 写真 | 添付ファイル | pic |
|  | スペース | map |
- JS/CSS設定は以下
 - https://js.cybozu.com/jquery/2.2.0/jquery.min.js
 - https://cdnjs.cloudflare.com/ajax/libs/blueimp-load-image/2.1.0/load-image.all.min.js
 - https://cdnjs.cloudflare.com/ajax/libs/openlayers/2.13.1/OpenLayers.js
 - ↓のサンプルファイル

```js
(function() {
    'use strict';

    // kintoneに添付されたファイルをダウンロード
    function getFile(url) {
        var df = new $.Deferred();
        var xhr = new XMLHttpRequest();

        xhr.open('GET', url, true);
        xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
        xhr.responseType = 'blob';

        xhr.onload = function(e) {
            if (this.status === 200) {
                df.resolve(this.response);
            }
        };

        xhr.send();
        return df.promise();
    }

    // 度分秒から百分率に変換
    function toPercentage(ref, geo) {
        if (ref === 'N' || ref === 'E') {
            return geo[0] + geo[1] / 60 + geo[2] / 3600;
        } else if (ref === 'S' || ref === 'W') {
            return -(geo[0] + geo[1] / 60 + geo[2] / 3600);
        }
    }

    kintone.events.on('app.record.detail.show', function(event) {
        var record = event.record;

        var space = kintone.app.record.getSpaceElement('map');
        var fileKey = record.pic.value[0].fileKey;
        var fileUrl = '/k/v1/file.json?fileKey=' + fileKey;

        $(space).append('<div id="canvas" style="width:400px; height:400px"></div>');

        var promise = getFile(fileUrl);
        promise.done(function(imageData) {
            loadImage.parseMetaData(imageData, function(data) {
                // 画像からロケーション情報を取得
                try {
                    var gpsLatitude = data.exif.get('GPSLatitude');
                    var gpsLatitudeRef = data.exif.get('GPSLatitudeRef');
                    var gpsLongitude = data.exif.get('GPSLongitude');
                    var gpsLongitudeRef = data.exif.get('GPSLongitudeRef');

                    var latitude = toPercentage(gpsLatitudeRef, gpsLatitude);
                    var longitude = toPercentage(gpsLongitudeRef, gpsLongitude);
                } catch(e) {
                    console.log('EXIF情報が含まれていません', e);
                    return false;
                }


                var map = new OpenLayers.Map('canvas');

                // 地図の表示
                var mapnik = new OpenLayers.Layer.OSM(
                    // デフォルトではhttp接続になるのでhttps接続に変更
                    'OpenStreetMap',
                    [
                        '//a.tile.openstreetmap.org/${z}/${x}/${y}.png',
                        '//b.tile.openstreetmap.org/${z}/${x}/${y}.png',
                        '//c.tile.openstreetmap.org/${z}/${x}/${y}.png'
                    ],
                    null
                );
                map.addLayer(mapnik);

                var lonLat = new OpenLayers.LonLat(longitude, latitude)
                    .transform(
                        new OpenLayers.Projection('EPSG:4326'),
                        new OpenLayers.Projection('EPSG:900913')
                    );
                map.setCenter(lonLat, 15);


                // マーカーの表示
                var markers = new OpenLayers.Layer.Markers('Markers');
                map.addLayer(markers);
                var marker = new OpenLayers.Marker(
                    new OpenLayers.LonLat(longitude, latitude).transform(
                        new OpenLayers.Projection('EPSG:4326'),
                        new OpenLayers.Projection('EPSG:900913')
                    )
                );
                markers.addMarker(marker);
            });
        });
    });

    kintone.events.on('app.record.index.show', function(event) {
        // 地図を表示済みの場合は一旦削除
        if ($('div#canvas').length > 0) {
            $('div#canvas').remove();
        }

        var space = kintone.app.getHeaderSpaceElement();
        $(space).append('<div id="canvas" style="width:80%; height:400px"></div>');

        var map = new OpenLayers.Map('canvas');

        var mapnik = new OpenLayers.Layer.OSM(
            'OpenStreetMap',
            [
                '//a.tile.openstreetmap.org/${z}/${x}/${y}.png',
                '//b.tile.openstreetmap.org/${z}/${x}/${y}.png',
                '//c.tile.openstreetmap.org/${z}/${x}/${y}.png'
            ],
            null
        );
        map.addLayer(mapnik);

        var markers = new OpenLayers.Layer.Markers('Markers');
        map.addLayer(markers);

        for (var i = 0; i < event.records.length; i++) {
            var record = event.records[i];

            var fileKey = record.pic.value[0].fileKey;
            var fileUrl = '/k/v1/file.json?fileKey=' + fileKey;

            var promise = getFile(fileUrl);

            promise.done(function(imageData) {
                loadImage.parseMetaData(imageData, function(data) {
                    try {
                        var gpsLatitude = data.exif.get('GPSLatitude');
                        var gpsLatitudeRef = data.exif.get('GPSLatitudeRef');
                        var gpsLongitude = data.exif.get('GPSLongitude');
                        var gpsLongitudeRef = data.exif.get('GPSLongitudeRef');

                        var latitude = toPercentage(gpsLatitudeRef, gpsLatitude);
                        var longitude = toPercentage(gpsLongitudeRef, gpsLongitude);
                    } catch(e) {
                        console.log('EXIF情報が含まれていません', e);
                        return false;
                    }

                    var lonLat = new OpenLayers.LonLat(longitude, latitude).transform(
                        new OpenLayers.Projection('EPSG:4326'),
                        new OpenLayers.Projection('EPSG:900913')
                    );
                    map.setCenter(lonLat, 15);

                    var marker = new OpenLayers.Marker(
                        new OpenLayers.LonLat(longitude, latitude).transform(
                            new OpenLayers.Projection('EPSG:4326'),
                            new OpenLayers.Projection('EPSG:900913')
                        )
                    );
                    markers.addMarker(marker);
                });
            });
        }
    });

})();
```

# 出来上がりのイメージ
## 詳細画面
こんな感じで地図が表示されます。
![IMG_2196.PNG](https://qiita-image-store.s3.amazonaws.com/0/102790/2480f975-b586-9852-970a-a7ccb45d6ea9.png)
写真の場所は鳥取砂丘の砂の美術館ですね。楽しかったな～

## 一覧画面
こんな感じで地図が表示されます。
![IMG_2197.PNG](https://qiita-image-store.s3.amazonaws.com/0/102790/e37bb6e3-42e8-a52b-4d56-020859bbf257.png)
プライバシー尊重のためにモザイクいれました・ｗ・
※写真を撮った距離が離れている場合は拡大率やどこを中心にするかを計算する必要があります。
　今回のサンプルではそこまで考慮していません。

#参考
## 梅田ロフトの場合
~~鳥取が田舎で~~地図のイメージがあまり掴めないのでこちらも・・・
![IMG_2198.PNG](https://qiita-image-store.s3.amazonaws.com/0/102790/ac68c33e-811b-6107-e96d-322991635d0f.png)


## kintoneでの写真/ビデオ添付手順
- 添付ファイルフィールドの参照欄をクリック
- ↓の画像のメニューが表示されるので添付方式を選択

![IMG_2193.PNG](https://qiita-image-store.s3.amazonaws.com/0/102790/7260dc19-7511-c980-f96a-e9ad1fd44431.png)

- 写真を選択
- ↓のメニューが表示されるので縮小のあり/なしを選択

![IMG_2194.PNG](https://qiita-image-store.s3.amazonaws.com/0/102790/aae4f412-bd25-ac8f-5e22-1e25ecca5baf.png)
※縮小版だとEXIF情報がカットされて地図表示できません


# kintone開発者ライセンス
kintone面白そうだな～と思った方は5ユーザー、1年間無償の[開発者ライセンス](https://cybozudev.zendesk.com/hc/ja/)を使ってみてください。