【アプリ開発 2】【Node.js JavaScript MongoDB】 JavaScriptを用いて端末の現在地を取得する。【2021年1月】

#Node.js Express MongoDBを用いたアプリ開発
Node.js Express MongoDBを用いたアプリ開発を行うことになりました。
開発の中で得られた知見を、支障のない範囲で記録していきたいと思います。

アプリの仕様上、各ユーザーの携帯電話の位置情報を取得し、googleマップ上で表示させる必要が生じました。
まずは、位置情報を取得する機能を実装してみます。

##要件
- ユーザーがログインし、「位置情報通知」ボタンをクリックした場合、位置情報がデーターベースに格納される。
- ユーザーがログアウトもしくは、「位置情報通知解除」ボタンをクリックした場合、位置情報がデーターベースから削除される。
- ２４時になった時点で、ユーザーの操作なしに、「位置情報通知解除」状態となる。
- ユーザーの位置が変更した場合、最新の位置情報がデータベースに格納される。
- 管理者は、定期的にデーターベースにアクセスしGoogleマップを用いて、ユーザーの位置を確認することができる。
- 位置情報には、ユーザーの連絡先と、コメント等が表示される。

##Javascriptを用いて位置情報を取得するテスト
とりあえずブラウザにアクセスした段階で、位置情報が取得できるようにしてみます。

##位置情報Geolocation API ついて
位置情報の取得についてh、Geolocation API を使用します。

Geolocation APIとは
>ウェブアプリでユーザーの位置情報を取得したいと思うことはよくあります。例えば、ユーザーの位置を地図上にプロットしたり、ユーザーの位置に関連するパーソナライズされた情報を表示したりすることができます。

>位置情報 APIは navigator.geolocation への呼び出しを介してアクセスします。これにより、ブラウザーはユーザーに自分の位置情報にアクセスする許可を要求します。ユーザーが許可すると、ブラウザーは端末上で利用可能な最良の機能を使用してこの情報にアクセスします (GPS など)。

https://developer.mozilla.org/ja/docs/Web/API/Geolocation_API

```html

<!DOCTYPE html>
<html lang="ja">

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>CAR MATCH APP</title>
</head>

<body>
  <div id="position-display">取得中</div>
  <script>
    // GPS センサの値が変化したら何らか実行する geolocation.watchPosition メソッド
    navigator.geolocation.watchPosition((position) => {
      let position_y = position.coords.latitude;            // 緯度を取得
      let position_x = position.coords.longitude;           // 経度を取得
      let accuracy = position.coords.accuracy;            // 緯度・経度の精度を取得
      displayData(position_y, position_x, accuracy);                    // displayData 関数を実行
    }, (error) => {                                     // エラー処理（今回は特に何もしない）
    }, {
      enableHighAccuracy: true                        // 高精度で測定するオプション
    });

    // データを表示する displayData 関数
    const displayData = (position_y, position_x, accuracy) => {
      var txt = document.getElementById("position-display");      
      txt.innerHTML = "緯度:" + position_y + " 経度 " + position_x + "<br>"  // データ表示
        + "精度: " + accuracy;
    }
  </script>
</body>

</html>
```
![IMG_BBE4EF911ADD-1.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547556/3ca9f691-11c7-5039-5d45-34b4e4660573.jpeg)


ページにアクセスするとブラウザ上にアクセスした端末の現在地が表示されます。

##スマホからも確認できるか試してみます。

netlifyに簡易的にアップロードします。

https://app.netlify.com/drop

その際、ファイル名はindex.htmlにします。また、アップロードはディレクトリで行う必要があるので、適当に"test/index.html"のような構造にして、testディレクトリをアップロードします。
アップロード方法はディレクトリをドラックするだけです。

スマホからアクセスしたところ、同様に確認することができました。

#node.jsから取得したデータをmongoDBに保存。
###ユーザーがページにアクセスしたら、位置情報がデータベースに登録できる様にします。

5秒毎に、データを更新して、mongoDBに保存します。

```js
<!DOCTYPE html>
<html lang="ja">

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>CAR MATCH APP</title>
</head>

<body>
  <div id="position-display">取得中</div>
  <script>

    const sendPosition = (latitude_i_y, longitude_k_x) => {//URLパラメータで遷移。
      location.href = `/savePosition?latitude_i_y=${latitude_i_y}&longitude_k_x=${longitude_k_x}`
    //ページ遷移し、savePositionメソッドを呼び出す。
    }

    // GPS センサの値が変化したら何らか実行する geolocation.watchPosition メソッド
    navigator.geolocation.watchPosition((position) => {
      let latitude_i_y = position.coords.latitude;            // 緯度を取得
      let longitude_k_x = position.coords.longitude;           // 経度を取得
      let accuracy = position.coords.accuracy;
      setTimeout(function () { //ページリロード後、5秒経過でsendPosition関数を呼び出し。
        sendPosition(latitude_i_y, longitude_k_x);
      }, 5000);
    },
      (error) => {                                    // エラー処理（今回は特に何もしない）
      }, {
      enableHighAccuracy: true                        // 高精度で測定するオプション
    });
  </script>
</body>

</html>
```
c_positionコントローラー.savePositionアクションを呼び出し。

```c_position.js
"use strict";

const Position = require("../models/m_position.js");
const Driver = require("../models/m_driver.js");

const getPosition = (req, res, next) => {
  res.render("v_getPosition");
}
const savePosition = (req, res, next) => {
  Driver.findOne({ name: "driverTaro" }).populate("position").exec((error, data) => {
   //ユーザーはテストで固定。本来はセッションから取得。該当するユーザーのpostionを更新する。
    Position.findOneAndUpdate(
      { _id: data.position._id },
      {
        $set: {
          latitude_i_y: req.query.latitude_i_y,
          longitude_k_x: req.query.longitude_k_x
        }
      }
    ).exec();
  })
  res.redirect("/getPosition");//再度位置情報取得へリダイレクト
}

module.exports = { getPosition, savePosition };
```

```json
{"_id":{"$oid":"5ff163eb514e011e79ccc284"},
"latitude_i_y":●●●.●●●●●●●●●●●●,
"longitude_k_x":●●●.●●●●●●●●●●●●,
"driver":{"$oid":"5ff163eb514e011e79ccc283"},
"__v":0}
```

保存を確認。
これでユーザーが任意に位置情報をアプリに提供できるようになりました。


データーベースから値を取得し、位置を表示させます。

googlemapを利用するため、まずはgooglemaps apiキーを取得します。
https://developers.google.com/maps/documentation/android-sdk/get-api-key?hl=ja
apiキーの埋め込みはソースコードで見えてしまうのでセキュリティー対策も必要です。
[参考]
(https://torisky.com/google-maps-api%E3%82%92%E5%88%A9%E7%94%A8%E3%81%99%E3%82%8B%E5%89%8D%E3%81%AB%E8%A8%AD%E5%AE%9A%E3%81%97%E3%81%A6%E3%81%8A%E3%81%8D%E3%81%9F%E3%81%84%E3%82%BB%E3%82%AD%E3%83%A5%E3%83%AA%E3%83%86/)

##mongoDBからデータを取得して、地図上に表示。
管理者側がユーザーの位置情報を知るために、googlemap上に表示させる様に実装します。

まず、ユーザーが登録している位置情報を返却するapiを作成します。

```js
const returnCarPositions = (req, res, next) => {
  Driver.find().populate('position').exec((error, data) => {
    res.json(data);
  })
}
```
ユーザー(Driver)モデルから全て取得し、位置情報をpopulateしjsonをレスポンスします。

続いて、管理者がアクセスするビューを作成します。

```js
<head>
  <title>CAR MATCH APP</title>
  <style>
    /* Google マップを表示させるためには style 内で width と height の指定が必要 */
    #mapDiv {
      width: 100%;
      height: 1000px;
    }
  </style>
</head>

<body>
  <div id="mapDiv"></div> <!-- 地図を置くdiv要素 -->
</body>

<script>
  const placeCars = () => {
    fetch('http://localhost:8080/admin/getCarPositions').then(response => response.json())
//fetchでapiを呼び出し。
      .then(data => {
        data.map(d => {
          // マーカーをつける
          const marker = new google.maps.Marker({
            position: { lat: d.position.latitude_i_y, lng: d.position.longitude_k_x },
            map: map
          });
        });
      })
  }
  function initMap() {
    navigator.geolocation.getCurrentPosition((position) => {
      var latitude_i_y = position.coords.latitude;         // 緯度を取得
      var longitude_k_x = position.coords.longitude;        // 経度を取得
      var initPos = new google.maps.LatLng(latitude_i_y, longitude_k_x); // 初期位置を指定
      map = new google.maps.Map(mapDiv, {        // Map オブジェクトを作成して mapDiv に表示
        center: initPos,
        zoom: 13,
      });
      placeCars();
    })
  }
</script>
<script id="googleMapUrl"
  src="https://maps.googleapis.com/maps/api/js?key=AIzaSyDl7XzTVOilq7dDEMXjWjJLXgjz6VCTymc&callback=initMap"></script>
動作確認
```
initMapは地図の初期設定を行う関数で、urlの末尾に**"callback=initMap"**という形で組み込まれているため、ページ読み込み時に呼び出されます。

initMap内でplaceCars関数を呼び出します。
placeCars関数で、先ほど作ったapiに対しfetchします。
返却されたデーターをmarkオブジェクトにし、initMapで作成したmapオブジェクトに埋め込み、マーカーを地図上に表示させます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547556/bc96bdb1-d2c1-fcb4-9a9a-794c4a150429.png)

この様に複数の箇所にマーカーを表示させることができました。

###今後の実装
現状は、ユーザーが任意のタイミングでブラウザを開き位置情報を送る必要があります。
これを、定期的に位置情報を送信するように実装したいと思います。
これには、service workerを用いて、サイトをPWA化する必要がありそうです。
もしくはネイティブアプリを作るか。。。

続く。

なお、ダミーデーターは東京付近の観光名所を指定してたんですが、どうしても表示させれずに途方にくれていました。もしやと思い地図をズームアウトしたところ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547556/815bc2ca-ff7d-8ac1-feb2-f813c010812b.png)

こんなところにいました。。。緯度と経度は、間違えないようにしようにしましょう。


参考：
[Geolocation_API](https://developer.mozilla.org/ja/docs/Web/API/Geolocation_API)
[こくぶん研究室](https://kkblab.com/make/javascript/gps.html)
[Node.jsとexpressを使って簡単なWebAPIを作成する]
(https://qiita.com/s_harada/items/8e9413b981cfbff4db2d)
[Google Maps JavaScript APIで複数マーカーをいい感じに表示する]
(https://qiita.com/KeisukeKudo/items/62ab555d19ddc8b6e068)