JavaScript で写真のGPS情報を取得し地図を表示する

こんにちは❗フリーランス・エンジニアの 九保すこひ です。

さてさて、最近は少しでかけにくくなったものの、その昔はよく「電車 + 折りたたみ自転車」で各地を巡ってそれをブログ記事にしていました。

しかし、毎回ブログを書くたびに「うーん・・・」となることがありました。

それが・・・・・・

地図の埋め込み&リンクをつくるのがメンドウ…😫

というものです。

ゆるり旅」に出ると、いくつかの場所に行くのですが、その全ての地図を用意するのはなかなか大変なんですね。

そして、何か対策は無いかと考えたら、ひとつアイデアが浮かびました。

写真のGPSデータから自動的にマップを用意すればいいじゃないか

と。

そこで❗

今回はJavaScriptを使って、選択された写真のExifデータから「緯度経度」を取得し、そのデータで該当する場所のマップ・HTMLタグを用意できるようにしてみます。

ぜひ何かの参考になりましたら嬉しいです😊✨

「この前、消費カロリー 4,500 kcal でした。
普段 1,000 もいかないのに😂」

開発環境: Bootstrap 5、Vue 3、Google Map

Google Embed Map の APIキーを取得する

今回使う地図は、Google Map(埋め込み版)です。

そのため、まずはGoogle Embed Map APIが使えるように「APIキー」を取得しましょう。

※ ちなみにGoogle Embed Map APIは、2021.8.17現在、完全無料で使えます。

では、Google Cloud にログインし、ページ上部からプロジェクトを選びます(もしない場合はつくってください)

そして、ページ左上の「ハンバーガーボタン」をクリックしてメニューを表示し、「APIとサービス」をクリックします。

ページが移動したら、「APIとサービスの有効化」をクリック。

すると、APIの検索ページが表示されるので、「maps embed api」で検索し「Maps Embed API」をクリックします。

そして、「有効にする」ボタンをクリックすればOKです。

では次に、Maps Embed APIにアクセスするための「APIキー」を取得します。
ページ左側にあるメニューから「認証情報」をクリック。

ページが移動したら、「Map Embed API」を選択し、「認証情報を作成」をクリックしてください。

すると、ポップアップが表示されるので「APIキー」をクリックします。

するとポップアップが表示され、その中にAPIキーがあるのでこれをコピーして控えておいてください。

※ ちなみにこのAPIキーにはドメインなどの制限が入っていないため、どのサイトでも&誰でも使えるようになっています。そのため、制限をかけたいかたは、「ちなみに 1: Google MapのAPIキーに制限をかける」を参考にしてみてください。

Exifデータを取得し、地図表示するコードをつくる

では、メインになるHTMLJavaScriptのコードです。

<html>
<head>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div id="app" class="p-5">
    <strong class="h3">JavaScript でGPSデータを取得し、地図を表示するサンプル</strong>
    <hr class="mb-4">
    <div class="row">
        <div class="col-12">
            <label class="btn btn-outline-primary">
                ファイルを選択する
                <input type="file" class="form-control d-none" accept="image/jpeg" @change="onFileChange">
            </label>
        </div>
        <div class="col-4">
            <img class="img-fluid mt-3" :src="image">
        </div>
        <div class="col-4" v-if="hasExif">
            <label>緯度</label>
            <div class="border border-2 p-2 rounded mb-2" v-text="latitude"></div>
            <label>経度</label>
            <div class="border border-2 p-2 rounded" v-text="longitude"></div>
            <div class="mt-4 overflow-auto p-3 bg-light" style="height:300px;" v-if="hasExif">
                おまけ: 抽出された Exif データ
                <hr>
                <div v-for="(value,key) in exif">
                    <strong v-text="key"></strong>:
                    <span v-text="value"></span>
                </div>
            </div>
        </div>
        <div class="col-4" v-if="hasExif">
            <iframe
                    class="w-100"
                    height="450"
                    :src="googleMapEmbedUrl" allowfullscreen>
            </iframe>
            <textarea class="form-control mt-2" rows="5" v-text="googleMapEmbedTag"></textarea>
            <a :href="googleMapLinkUrl" class="btn btn-outline-primary mt-3" target="_blank">Googleマップで確認する</a>
        </div>
    </div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/js/bootstrap.min.js"></script>
<script src="https://unpkg.com/vue@3.1.1/dist/vue.global.prod.js"></script>
<script src="https://cdn.jsdelivr.net/npm/exif-js"></script>
<script>

    Vue.createApp({
        data() {
            return {
                exif: {},
                googleMapApiKey: '(ここにあなたのAPIキーを書き込む)'
            }
        },
        methods: {
            getExif(key) {

                try {

                    return this.exif[key];

                } catch(e) {}

                return '';

            },
            getGps(key) {   // `Latitude` or `Longitude`

                const gps = this.getExif('GPS'+ key);
                const ref = this.getExif('GPS'+ key +'Ref');

                if(gps instanceof Array) {

                    const degrees = gps[0];
                    const minutes = gps[1];
                    const seconds = gps[2];
                    let dd = degrees + minutes/60 + seconds/(60*60);

                    if (['S', 'W'].includes(ref)) { // 南半球、西経の場合はマイナス

                        dd *= -1;

                    }

                    return dd

                }

                return '';

            },
            onFileChange(e) {

                const files = e.target.files;

                if(files.length > 0) {

                    this.exif = {};
                    const file = files[0];
                    const reader = new FileReader();
                    reader.addEventListener('load', () => {

                        this.image = reader.result;
                        EXIF.getData(file, () => {  // Exif を取り出す

                            this.exif = EXIF.getAllTags(file);

                        });

                    });
                    reader.readAsDataURL(file);

                }

            }
        },
        computed: {
            hasExif() {

                return (Object.keys(this.exif).length > 0);

            },
            latitude() { // 緯度

                return this.getGps('Latitude');

            },
            longitude() { // 経度

                return this.getGps('Longitude');

            },
            googleMapEmbedUrl() {

                const key = this.googleMapApiKey;
                const latitude = this.latitude;
                const longitude = this.longitude;
                return `https://www.google.com/maps/embed/v1/place?key=${key}&q=${latitude},${longitude}`;

            },
            googleMapEmbedTag() {

                return `<iframe width="450" height="450" src="${this.googleMapEmbedUrl}" allowfullscreen></iframe>`;

            },
            googleMapLinkUrl() {

                const latitude = this.latitude;
                const longitude = this.longitude;
                return `https://www.google.com/maps/search/?api=1&query=${latitude},${longitude}`;

            }
        }
    }).mount('#app');

</script>
</body>
</html>

この中で実際にExifデータを取得しているのは、onFileChange()の中ですが、「exif-js」というパッケージを使っています。

(こういったパッケージは本当に助かりますね。感謝です❗)

なお、先ほど取得したAPIキーは「(ここにあなたのAPIキーを書き込む)」の部分と入れ替えてください。

また、getGps()は少し複雑なのですが、写真のExifデータから取得できるGPS情報は「DMS」とよばれる{度、分、秒}形式になっているので、「DD(Decimal degrees)」と呼ばれる形式に変換しています。

ちなみに 1: Google MapのAPIキーに制限をかける

今のままでは「どのサイトでも&どのAPIにも」使えるようになっているので、勝手にAPIキーを使われてしまう可能性もあります。

そこで、APIキーに制限をかける方法をご紹介します。
制限をかけるのは、次の2つです。

  • 他のドメインでは使えないようにする
  • 今回のAPIキーを Google マップ以外には使えないようにする

では、さきほど作ったAPIキーが「認証情報ページ」に表示されているので編集ボタンをクリックしてください。

ページ移動したら、「アプリケーションの制限」の中から「HTTP リファラー」を選択して「項目を追加」をクリックしてください。

すると、リファラーを登録するポップアップが表示されるので、以下のようにドメインを入力します。

*はワイルドカードなので、「なんでもOK」という意味になります。)

次に、利用できるAPIの制限を設定します。

APIの制限」から「キーを制限」を選択し、すぐ下にあるセレクトボックスをクリックします。

すると、APIの一覧が表示されるので、この中から「Maps Embed API」にチェックをいれて「OK」をクリックします。

あとは、「保存」ボタンをクリックすれば完了です。

ちなみに 2: スマホの写真にGPSが埋め込まれていない場合

スマホの設定でGPS埋め込みを変更することができます。
やり方は、以下を参考にしてみてください。

📝 参考ページ:

デモを用意しました

今回の機能を試すことができるデモページをご用意しました。
ぜひお試しください。(データは一切送信しません)

📝 デモページ

企業様へのご提案

今回の機能を使うと以下のようなことに使えます。

  • お店の外観写真にGPSデータを埋め込んでおけば、後で地図の位置を指定する手間を省くことができます
  • 写真を送ってもらうだけで、その人のいる場所や時間を知ることができるので、作業完了報告などに応用することができる
  • 連続した写真を撮影することで、1日の動きを地図上に表示することができる
  • いわゆる「置き配」の証拠として撮影した写真から、どの場所かをすぐ特定できる

もしこういった機能をご希望でしたらいつでもお気軽に「お問い合わせ」からご連絡ください。m(_ _)m

おわりに

ということで、今回はJavaScriptExifデータを抜き出し、地図表示をしてみました。

Exifデータは、自宅の場所がバレたりしてしまうので、一時期問題になりましたが、ビジネスにからめてみると、とても便利なので有効活用しやすいんじゃないでしょうか。

なお、上記の理由からアップロードされたExifデータは基本的に削除することが望ましいですが、その方法は以下の記事を参考にしてみてください。

📝 参考ページ: PHPでEXIFデータを操作する方法(取得、書き込み、削除)

ぜひ皆さんもやってみてくださいね。

ではでは〜❗

「各地ではその土地の
おいしいものをいただきます✨」

開発のご依頼お待ちしております 😊✨ お問い合わせ
また、こちらもお待ちしております。
  • 実案件の開発サポート: 詳細
  • ツイッターのフォロー: 詳細
どうぞよろしくお願いいたします!
このエントリーをはてなブックマークに追加       follow us in feedly