JavaScript | Web API:位置情報・センサー - watchPosition

JavaScript JavaScript
スポンサーリンク

watchPosition は「動いたらそのたびに現在地を教えてもらう」仕組み

前回の getCurrentPosition は「今どこ?」を一回だけ聞く API でした。
watchPosition はそれの“連続版”です。

ユーザーが移動するたびに、
「今ここにいるよ」と位置情報を何度も通知してくれる仕組み

ランニングの軌跡を記録したい
配達員の位置をリアルタイムに追いたい
マップ上の自分のアイコンをスーッと動かしたい

こういう「動き続ける位置」を扱いたいときに使うのが watchPosition です。


基本の形をまず押さえる

getCurrentPosition との違いをコードで見る

getCurrentPosition:

navigator.geolocation.getCurrentPosition(success, error, options);
JavaScript

watchPosition:

const watchId = navigator.geolocation.watchPosition(success, error, options);

// 監視をやめる
navigator.geolocation.clearWatch(watchId);
JavaScript

一番の違いはここです。

  • getCurrentPosition … 成功コールバックが「1 回だけ」呼ばれる
  • watchPosition … 位置が変わるたびに「何度も」成功コールバックが呼ばれる

そして watchPosition は「監視 ID(watchId)」を返します。
この ID を clearWatch に渡すことで、「もう追跡しなくていいよ」と止められます。


最小の「現在位置を追跡して表示する」例

HTML と JavaScript の全体像

HTML:

<button id="startWatchBtn">追跡開始</button>
<button id="stopWatchBtn" disabled>追跡停止</button>
<pre id="log">まだ追跡していません</pre>

JavaScript:

const startWatchBtn = document.querySelector("#startWatchBtn");
const stopWatchBtn = document.querySelector("#stopWatchBtn");
const log = document.querySelector("#log");

let watchId = null;

startWatchBtn.addEventListener("click", () => {
  if (!("geolocation" in navigator)) {
    log.textContent = "このブラウザは位置情報に対応していません";
    return;
  }

  if (watchId !== null) {
    log.textContent = "すでに追跡中です";
    return;
  }

  log.textContent = "位置情報の追跡を開始します…";

  watchId = navigator.geolocation.watchPosition(
    (position) => {
      const c = position.coords;
      log.textContent =
        `緯度: ${c.latitude}\n` +
        `経度: ${c.longitude}\n` +
        `精度: 約 ${c.accuracy} m\n` +
        `取得時刻: ${new Date(position.timestamp).toLocaleString()}`;
    },
    (error) => {
      log.textContent = `エラー: ${error.message}`;
    },
    {
      enableHighAccuracy: true,
      timeout: 10000,
      maximumAge: 0
    }
  );

  startWatchBtn.disabled = true;
  stopWatchBtn.disabled = false;
});

stopWatchBtn.addEventListener("click", () => {
  if (watchId !== null) {
    navigator.geolocation.clearWatch(watchId);
    watchId = null;
    log.textContent = "追跡を停止しました";
    startWatchBtn.disabled = false;
    stopWatchBtn.disabled = true;
  }
});
JavaScript

ここでやっていることを日本語で整理すると、

  • 「追跡開始」ボタンで watchPosition を呼び、watchId を保存
  • 位置が更新されるたびに success コールバックが呼ばれ、ログを書き換える
  • 「追跡停止」ボタンで clearWatch(watchId) を呼び、監視を終了

という流れです。


watchPosition の“連続性”をちゃんとイメージする

「1 回の結果」ではなく「時間とともに変わる値」を扱う

getCurrentPosition は「1 回だけ値をもらって終わり」なので、
普通の関数の戻り値に近い感覚で扱えます。

一方 watchPosition は、

  • いつコールバックが呼ばれるか分からない
  • 何回呼ばれるかも分からない
  • ユーザーが止まっていればしばらく呼ばれないこともある

という「時間に開いたイベント」です。

だからこそ、こういう書き方になります。

let lastPosition = null;

watchId = navigator.geolocation.watchPosition((position) => {
  if (lastPosition) {
    // 前回からどれだけ移動したかを計算できる
  }
  lastPosition = position;
});
JavaScript

「最新の位置をどこかに保持しておく」
「前回との差分を使って距離を計算する」

といった“継続的な状態管理”が、watchPosition を使うときのポイントです。


ランニング距離をざっくり計算するミニ例

「追跡するなら、何か意味のあることをしたいよね」ということで、
超ざっくりですが「移動距離」を足し上げる例を見てみます。

※ 正確な距離計算ではなく、イメージ用の簡易版です。

HTML:

<button id="startRunBtn">ラン開始</button>
<button id="stopRunBtn" disabled>ラン停止</button>
<pre id="runLog">まだ開始していません</pre>

JavaScript:

const startRunBtn = document.querySelector("#startRunBtn");
const stopRunBtn = document.querySelector("#stopRunBtn");
const runLog = document.querySelector("#runLog");

let watchId = null;
let lastCoords = null;
let totalDistance = 0; // メートル

function toRad(deg) {
  return (deg * Math.PI) / 180;
}

function distanceInMeters(lat1, lng1, lat2, lng2) {
  const R = 6371000; // 地球の半径(m)
  const dLat = toRad(lat2 - lat1);
  const dLng = toRad(lng2 - lng1);
  const a =
    Math.sin(dLat / 2) ** 2 +
    Math.cos(toRad(lat1)) *
      Math.cos(toRad(lat2)) *
      Math.sin(dLng / 2) ** 2;
  const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
  return R * c;
}

startRunBtn.addEventListener("click", () => {
  if (!("geolocation" in navigator)) {
    runLog.textContent = "このブラウザは位置情報に対応していません";
    return;
  }

  if (watchId !== null) {
    runLog.textContent = "すでに計測中です";
    return;
  }

  totalDistance = 0;
  lastCoords = null;
  runLog.textContent = "ラン開始。位置情報を追跡中…";

  watchId = navigator.geolocation.watchPosition(
    (position) => {
      const c = position.coords;

      if (lastCoords) {
        const d = distanceInMeters(
          lastCoords.latitude,
          lastCoords.longitude,
          c.latitude,
          c.longitude
        );
        totalDistance += d;
      }

      lastCoords = c;

      runLog.textContent =
        `現在の位置:\n` +
        `緯度: ${c.latitude}\n` +
        `経度: ${c.longitude}\n` +
        `累計距離: ${(totalDistance / 1000).toFixed(3)} km`;
    },
    (error) => {
      runLog.textContent = `エラー: ${error.message}`;
    },
    {
      enableHighAccuracy: true,
      timeout: 10000,
      maximumAge: 0
    }
  );

  startRunBtn.disabled = true;
  stopRunBtn.disabled = false;
});

stopRunBtn.addEventListener("click", () => {
  if (watchId !== null) {
    navigator.geolocation.clearWatch(watchId);
    watchId = null;
    runLog.textContent += "\n計測を終了しました。";
    startRunBtn.disabled = false;
    stopRunBtn.disabled = true;
  }
});
JavaScript

ここでの大事なポイントは、

  • lastCoords に「前回の位置」を保存しておく
  • 新しい位置が来るたびに「前回との差」を距離として足し上げる
  • 監視を止めるタイミングをボタンで制御する

という「連続データを扱う思考」です。


options が watchPosition で特に重要になる理由

enableHighAccuracy とバッテリーの関係

watchPosition は「ずっと位置を取り続ける」ので、
enableHighAccuracy: true のまま長時間動かすと、
スマホのバッテリーをかなり食います。

だからこそ、用途に応じて考える必要があります。

  • ランニング計測 → enableHighAccuracy: true(短時間・高精度)
  • 配達員の位置をざっくり追う → false でもよい(長時間・省電力)

「とりあえず true」ではなく、
“どれくらいの精度が本当に必要か”を考える癖 をつけると、設計が一段上がります。

maximumAge で「どこまで古い位置を許すか」を決める

watchPosition でも maximumAge は効きます。

  • 0 … 毎回最新を取りに行く
  • 5000 … 5 秒以内の位置ならキャッシュを使ってよい

例えば、1 秒ごとに位置が更新されれば十分なアプリなら、
maximumAge を少し大きめにしても体感は変わりません。

その代わり、位置取得の回数が減ってバッテリーが節約できます。

「どれくらい“リアルタイム”である必要があるか」を考えて、
maximumAge を調整するのも、watchPosition 設計の一部です。


監視を“止める”ことまで含めて設計する

clearWatch を忘れるとどうなるか

watchPosition は、呼んだ瞬間から「ずっと動き続ける」タイプの API です。
clearWatch を呼ばない限り、位置情報の取得は続きます。

その結果、

  • バッテリーを無駄に消費する
  • 位置情報アイコンがずっと点灯し続ける
  • ユーザーに「このサイト怪しくない?」と思われる

といったことが起こります。

だからこそ、

  • ページを離れるとき
  • 特定の操作が終わったとき
  • 一定時間経ったとき

など、どこかで必ず clearWatch を呼ぶ設計にしておくべきです。

ページ離脱時に自動で止める例

window.addEventListener("beforeunload", () => {
  if (watchId !== null) {
    navigator.geolocation.clearWatch(watchId);
  }
});
JavaScript

こうしておくと、
ユーザーがページを閉じたりリロードしたときに、
位置情報の監視がちゃんと止まります。

「開始」と「停止」をセットで考えること。
これが watchPosition を扱ううえでの重要なマインドセットです。


初心者として「watchPosition」で本当に掴んでほしいこと

watchPosition の本質は、
「位置が変わるたびに、何度も現在地を教えてもらう仕組み」
です。

そのうえで、まずこの 4 つをしっかり押さえてください。

  • watchPosition は「連続的な位置更新」、clearWatch で必ず止める
  • コールバックは何度も呼ばれるので、「前回の位置」などの状態を自分で持つ
  • enableHighAccuracymaximumAge で「精度 vs バッテリー」を調整する
  • 追跡開始・停止の UI をきちんと用意して、ユーザーに分かる形で制御させる

練習としては、

  • 追跡開始/停止ボタン付きで、現在地をログ表示する
  • 前回位置との差分を使って「ざっくり距離」を足し上げてみる

この 2 段階を書けるようになるだけで、
位置情報を「一回取る」世界から「動きを扱う」世界に、一歩踏み込めます。

そこまで行くと、
地図上でアイコンを動かす
ランニングアプリのプロトタイプを作る

みたいなアイデアが、急に現実味を帯びてきます。
watchPosition は、その扉を開けるためのキーみたいな存在です。

タイトルとURLをコピーしました