Geolocation APIを使って位置情報を取得するサンプルをReactで書いてみる

## Geolocation APIを使った、Reactのサンプルを作成してみる

`Geolocation API` というものをちゃんと触ったことがなかったことに気づき、基本的な機能を使ったサンプルをReactを使いながら試してみたいと思います。

基本的に、MDNのGeolocation APIのドキュメントを読みながら勧めていきます。

https://developer.mozilla.org/ja/docs/Web/API/Geolocation/Using_geolocation


なお、`create-react-app`を使って作成したものを書き換えていく形でコードを書いています。
実際に書いた自身のソースコードはGithubに上げています。
https://github.com/shinshin86/geolocation-api-react-sample


## getCurrentPositionメソッドで位置情報を取得する

`navigator.geolocation.getCurrentPosition` を用いて、位置情報は取得します。
ソースコードを読んでもらうのが早いと思いますが、このメソッドを実行すると軽度・緯度情報を取得できるので、あとはそれを処理の中で使うだけです。

`getCurrentPosition` の初回呼び出し時にブラウザに位置情報を共有するかを聞かれると思うので、許可してください。
許可すれば、位置情報が画面上に表示されるかと思います。

また、余談ですが、
最近`React hooks`を積極的に使っていこうと勉強中のために、コードに組み込むように努力しています。
使い方が怪しいところなどあれば、コメント頂けますと幸いです。

コメントをはさみつつ、コードを下に貼ります。

```javascript
import React, { useState, useEffect, useRef } from 'react';
import './App.css';

/* エラーテキスト */
const ErrorText = () => (
  <p className="App-error-text">geolocation IS NOT available</p>
);

export default () => {
  const [isAvailable, setAvailable] = useState(false);
  const [position, setPosition] = useState({ latitude: null, longitude: null });

  // useEffectが実行されているかどうかを判定するために用意しています
  const isFirstRef = useRef(true);

  /*
   * ページ描画時にGeolocation APIが使えるかどうかをチェックしています
   * もし使えなければその旨のエラーメッセージを表示させます
   */
  useEffect(() => {
    isFirstRef.current = false;
    if ('geolocation' in navigator) {
      setAvailable(true);
    }
  }, [isAvailable]);

  const getCurrentPosition = () => {
    navigator.geolocation.getCurrentPosition(position => {
      const { latitude, longitude } = position.coords;
      setPosition({ latitude, longitude });
    });
  };

  // useEffect実行前であれば、"Loading..."という呼び出しを表示させます
  if (isFirstRef.current) return <div className="App">Loading...</div>;

  return (
    <div className="App">
      <p>Geolocation API Sample</p>
      {!isFirstRef && !isAvailable && <ErrorText />}
      {isAvailable && (
        <div>
          <button onClick={getCurrentPosition}>Get Current Position</button>
          <div>
            latitude: {position.latitude}
            <br />
            longitude: {position.longitude}
          </div>
        </div>
      )}
    </div>
  );
};
```


`getCurrentPosition()` はオプションとして第二引数にコールバック関数を指定することで、エラー発生時に実行される関数を指定することもできますが、ここではしていません。

ここらへんの詳細はMDNのドキュメントを参照していただけたらと思います。
https://developer.mozilla.org/ja/docs/Web/API/Geolocation/getCurrentPosition



## watchPositionメソッドで現在の位置情報を監視する

先程のソースコードに、さらに `watchPosition`メソッドを用いた機能を追加しました。
watchPositionは一度実行すると位置情報が変化した際(デバイスが移動した時や、より正確な位置情報が得られた時) に都度呼び出されるので、移動に応じて位置情報を更新したりするときなどに使えます。
使い方は基本的にgetCurrentPositionと同じです。


またwatchPositionによる監視を終了させたい場合は、clearWatchメソッドを用います。
watchPositionを実行した際に、watch IDが返されるのですが、これをclearWatchメソッドの引数にして実行することで監視を終了できます。

詳細は下記のソースコードを読んでいただけたらと思います。
(ところどころ醜いところがあるかもしれませんが...)

```javascript
import React, { useState, useEffect, useRef } from 'react';
import './App.css';

const ErrorText = () => (
  <p className="App-error-text">geolocation IS NOT available</p>
);

export default () => {
  const [isAvailable, setAvailable] = useState(false);
  const [position, setPosition] = useState({ latitude: null, longitude: null });
  const [watchStatus, setWatchStatus] = useState({
    isWatching: false,
    watchId: null
  });

  // useEffectが実行されているかどうかを判定するために用意しています
  const isFirstRef = useRef(true);

  useEffect(() => {
    isFirstRef.current = false;
    if ('geolocation' in navigator) {
      setAvailable(true);
    }
  }, [isAvailable]);

  const getCurrentPosition = () => {
    navigator.geolocation.getCurrentPosition(position => {
      const { latitude, longitude } = position.coords;
      setPosition({ latitude, longitude });
    });
  };

  /*
   * 監視を開始します
   */
  const startWatchPosition = () => {
    const watchId = navigator.geolocation.watchPosition(position => {
      const { latitude, longitude } = position.coords;
      setPosition({ latitude, longitude });
    });

    setWatchStatus({ isWatching: true, watchId });
  };

  /*
   * 監視を停止します
   */
  const stopWatchPosition = watchStatus => {
    navigator.geolocation.clearWatch(watchId);
    setWatchStatus({ isWatching: false, watchId });
  };

  // useEffect実行前であれば、"Loading..."という呼び出しを表示させます
  if (isFirstRef.current) return <div className="App">Loading...</div>;

  const { isWatching, watchId } = watchStatus;

  return (
    <div className="App">
      <h2>Geolocation API Sample</h2>
      {!isFirstRef && !isAvailable && <ErrorText />}
      {isAvailable && (
        <div>
          <button onClick={getCurrentPosition}>Get Current Position</button>
          {isWatching ? (
            <button onClick={() => stopWatchPosition(watchStatus)}>
              Stop Watch Position
            </button>
          ) : (
            <button onClick={startWatchPosition}>Start Watch Position</button>
          )}
          <div>
            <h3>Position</h3>
            <div>
              latitude: {position.latitude}
              <br />
              longitude: {position.longitude}
            </div>
          </div>
          <div>
            <h3>Watch Mode</h3>
            <p>Watch Status: {isWatching ? 'Watching' : 'Not Watching'}</p>
            <p>Watch ID: {watchId}</p>
          </div>
        </div>
      )}
    </div>
  );
};
```

watchPosition, clearWatchについても詳細な使い方はMDNのドキュメントを参照いただけたらと思います。

watchPosition
https://developer.mozilla.org/ja/docs/Web/API/Geolocation/watchPosition

clearWatch
https://developer.mozilla.org/ja/docs/Web/API/Geolocation/clearWatch

非常に簡単なサンプルのみとなりますが、これで以上となります。