📍

ウェブ地図の「現在地機能」はどう動いているのか - Geolocation API とMapLibre GL JSの仕組み

に公開

「MapLibre GL JS」は、ウェブ地図をつくるためのオープンソースのTypeScriptライブラリです 🗺️

MapLibre GL JSによるウェブ地図の例

https://maplibre.org/

これは、Mapbox社の「Mapbox GL JS」から派生したものです。2020年12月、Mapbox社がこのライブラリをv2へアップグレードした際にOSS化し、それを受けてオープンソース版(v1)をフォークし
たMapLibreプロジェクトが立ち上がりました。これにより、MapLibre GL JSはMapbox GL JSと(一定以上の)互換性を持ちつつも、コミュニティ主導で開発が進められる、MITライセンスの代替ライブラリとなりました。

https://www.mapbox.com/mapbox-gljs

MapLibre/Mapbox GL JS のどちらにも「現在地機能」があります。ユーザーが現在いる位置を特定し、地図上に表示するものです。Googleマップなど他のウェブ地図でも見られる機能ですが、具体的にこれはどのように動作しているのでしょうか?

ウェブでの位置情報取得: Geolocation API

まず、ウェブ上でユーザーの現在地を特定するには、ブラウザが提供する「Geolocation API」を使います。

このAPIは、ウェブサイトがユーザーの許可を得て、GPSやWi-Fi測位、携帯電話の基地局情報(Cell ID)などを利用して位置情報を取得する機能を提供します。

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

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

このAPIを利用する上での重要なポイントは以下の2点です。

  • HTTPS環境でのみ動作する: セキュリティ上の理由から、位置情報を扱うにはHTTPSで配信されるウェブサイトが必要です。
  • ユーザーの明示的な許可が必要: 位置情報の取得を試みる際、ブラウザはユーザーに対して「位置情報の利用を許可しますか?」というダイアログを表示します。ユーザーが拒否した場合、位置情報は取得できません。

Geolocation APIには、主に2つのメソッドがあります。

  • Geolocation.getCurrentPosition(): ユーザーの現在の位置を一度だけ取得します。
  • Geolocation.watchPosition(): ユーザーが移動するなどして位置情報が変化するたびに、継続的に位置情報を取得・更新します。

端末の現在位置を一度だけ取得 - getCurrentPosition()

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

このメソッドは、非同期処理でユーザーの現在地を一度だけ取得します。引数として、成功時の処理(コールバック関数)、エラー時の処理(コールバック関数)、そしてオプション設定を渡します。

navigator.geolocation.getCurrentPosition((position) => {
  console.log("✅ 現在地の情報を取得しました!");
  console.log("緯度(latitude):", position.coords.latitude);
  console.log("経度(longitude):", position.coords.longitude);
  console.log("精度(accuracy):", position.coords.accuracy, "メートル");
});
💡 より詳細なコード例
// Geolocation API が利用可能かチェック
if ("geolocation" in navigator) {
  // 現在地を取得 - navigator.geolocation.getCurrentPosition() メソッド
  // 非同期で実行され、3つの引数を取る:
  // 1. 成功時のコールバック関数
  // 2. エラー時のコールバック関数
  // 3. オプション設定
  navigator.geolocation.getCurrentPosition(
    // -------------------------
    // 1. 成功時のコールバック関数
    // -------------------------
    // `GeolocationCoordinates`
    // https://developer.mozilla.org/ja/docs/Web/API/GeolocationCoordinates
    (position) => {
      console.log("✅ 現在地の情報を取得しました!");
      console.log("緯度(latitude):", position.coords.latitude);
      console.log("経度(longitude):", position.coords.longitude);
      console.log("精度(accuracy):", position.coords.accuracy, "メートル");

      // 方向・速度・高度・高度精度: デバイスや取得方法によって利用できない(nullになる)場合がある
      console.log("進行方向(heading):", position.coords.heading, "度");
      console.log("速度(speed):", position.coords.speed, "メートル/秒");
      console.log("高度(altitude):", position.coords.altitude, "メートル");
      console.log(
        "高度精度(altitudeAccuracy):",
        position.coords.altitudeAccuracy,
        "メートル",
      );

      const date = new Date(position.timestamp);
      console.log("時刻:", date.toLocaleString());
    },

    // -------------------------
    // 2. エラー時のコールバック関数
    // -------------------------
    // GeolocationPositionError
    // https://developer.mozilla.org/ja/docs/Web/API/GeolocationPositionError
    (error) => {
      console.error(`⚠️ ERROR(${error.code}): ${error.message}`);
      switch (error.code) {
        case error.PERMISSION_DENIED:
          console.error("ユーザーが位置情報の利用を拒否しました。");
          break;
        case error.POSITION_UNAVAILABLE:
          console.error("位置情報が利用できません。");
          break;
        case error.TIMEOUT:
          console.error("位置情報の取得がタイムアウトしました。");
          break;
        default:
          console.error("原因不明のエラーです:", error.message);
          break;
      }
    },

    // -------------------------
    // 3. オプション設定
    // -------------------------
    {
      // enableHighAccuracy: より高精度な位置情報の取得を試みる
      // 例えば、GPSやWi-Fi測位、携帯電話の基地局情報(Cell ID)を利用する
      // レスポンス時間が増加したり、バッテリー消費が増える可能性がある
      // デフォルトはfalse
      enableHighAccuracy: true,
      // timeout: タイムアウト時間をミリ秒で設定する
      // この時間内に位置情報が取得できない場合、エラーコールバックが呼ばれる
      // デフォルトはInfinity(無制限)
      timeout: 5000,
      // maximumAge: キャッシュされた位置情報の有効期限をミリ秒で設定する
      // 0に設定すると、キャッシュを用いず、常に新しい位置情報を取得しようとする
      // デフォルトは0
      maximumAge: 0,
    },
  );
} else {
  // ブラウザがAPIに対応していない場合の処理
  console.log("⚠️ このブラウザはGeolocation APIに対応していません。");
}

端末の位置が変化するごとに取得・更新 - watchPosition()

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

このメソッドは、ユーザーの位置が変化するたびに自動的にコールバック関数を実行し、最新の位置情報を取得し続けます。これにより、ユーザーの移動に合わせて地図をリアルタイムで更新するようなアプリケーションを実装できます。

watchPosition()は、監視を停止するために使う 「監視ID」 を返します。監視を停止したい場合は、navigator.geolocation.clearWatch()メソッドにこのIDを渡します。

const watchID = navigator.geolocation.watchPosition(
  // 位置情報が更新されるたびに実行される
  (position) => {
    const { latitude, longitude } = position.coords;
    const date = new Date(position.timestamp);
    console.log(
      `緯度: ${latitude}, 経度: ${longitude} (取得時刻: ${date.toLocaleString()})`,
    );
  },
);

// 監視を停止する
navigator.geolocation.clearWatch(watchID);
💡 より詳細なコード例
// Geolocation API が利用可能かチェック
if ("geolocation" in navigator) {
  console.log("🟢 位置情報の監視を開始します...");

  // watchPosition()は「監視ID」を返す
  // このIDは監視を停止する際に使用する
  const watchID = navigator.geolocation.watchPosition(
    // -------------------------
    // 1. 成功時のコールバック関数
    // -------------------------
    // 位置情報が更新されるたびに実行される
    (position) => {
      const { latitude, longitude } = position.coords;
      const date = new Date(position.timestamp);
      console.log(
        `📍 新しい位置情報 - 緯度: ${latitude}, 経度: ${longitude} (取得時刻: ${date.toLocaleString()})`,
      );

      // ここに、取得した位置情報を使った処理(地図の更新など)を追加する
    },

    // -------------------------
    // 2. エラー時のコールバック関数
    // -------------------------
    // 監視中にエラーが発生した場合に実行される
    (error) => {
      console.error(
        "⚠️ 位置情報の監視中にエラーが発生しました:",
        error.code,
        error.message,
      );
    },

    // -------------------------
    // 3. オプション設定
    // -------------------------
    // `getCurrentPosition()`と同様のオプションを設定できる
    {
      enableHighAccuracy: true,
      maximumAge: 0,
    },
  );

  // clearWatch()メソッドにより、監視を停止する
  // この例では、5秒後に停止
  setTimeout(() => {
    navigator.geolocation.clearWatch(watchID);
    console.log("🔴 位置情報の監視を停止しました。");
  }, 5000);
} else {
  // ブラウザがAPIに対応していない場合の処理
  console.log("⚠️ このブラウザはGeolocation APIに対応していません。");
}

参考: SmartHRの勤怠管理機能での事例

以下の記事(2025年4月)では、SmartHRの岩元(@yoiwamoto)さんから、「勤怠管理」機能での「打刻時の位置情報取得」の開発からの知見が紹介されていて参考になります。

https://tech.smarthr.jp/entry/kintai-geolocation-api

例えば、位置情報の取得には時間がかかることがあるため、ユーザーの操作を妨げないよう、取得完了を待たずに操作を完了させたり、watchPosition() であらかじめ取得しておくといった対応が取られていました。

また、<iframe>内や、スマホアプリのWebView環境でのGeolocation APIの利用はデフォルトでは制限されているため、その対応についても解説されています。

参考: Chromeでの現在地の変更

Chromeブラウザの開発者ツール(DevTools)では、GPSなどから取得された「実際の現在地」ではなく、任意の場所(経緯度)を設定できます。以下の記事で解説しました:

https://zenn.dev/mierune/articles/c2ec78fb16bc64

参考: スマホアプリ(ネイティブ)での位置情報取得

ウェブでの位置情報取得はここまで解説した通りですが、スマホのネイティブアプリでの取得は、また異なる仕組みとなっています。

位置情報にまつわるトピックのを扱う勉強会 MIERUNE Meetup mini #05での、井上晴稀(@kyotonagoya1476さんが取り上げた以下も参考が参考になるでしょう。

https://speakerdeck.com/kyotonagoya/mierune-meetup-mini-2022-12-15-fa-biao-zi-liao

MapLibre GL JS の現在地機能: GeolocateControl

https://maplibre.org/maplibre-gl-js/docs/API/classes/GeolocateControl/

MapLibre GL JSは「GeolocateControl」という機能を提供しています。この機能を使うと、Geolocation APIを利用してユーザーの現在地を取得し、その位置を地図上にマーカーとして表示するボタンを追加できます。

MapLibre GL JS - GeolocateControlの画面例

基本的な使い方

https://maplibre.org/maplibre-gl-js/docs/examples/locate-the-user/

GeolocateControlは、以下のコード例のように追加します。HTMLを含んだ完全な例は、上記のドキュメントをご参照ください。

// MapLibre地図の作成
const map = new maplibregl.Map({
  container: "map",
  style: "https://tiles.openfreemap.org/styles/bright",
  center: [139.74136111, 35.68111667],
  zoom: 12
});

// 地図にユーザーの位置情報を表示するコントロールを追加
map.addControl(
  new maplibregl.GeolocateControl({
    positionOptions: {
      enableHighAccuracy: true,
    },
    trackUserLocation: true,
  })
);

オプション設定 - GeolocateControlOptions

https://maplibre.org/maplibre-gl-js/docs/API/type-aliases/GeolocateControlOptions/

GeolocateControlには5つのオプションがあります。これらはすべて任意で、指定しない場合はデフォルト値が適用されます。

https://github.com/maplibre/maplibre-gl-js/blob/c437b82ea8a4f93b5cb9c3fce4f486ddb717951f/src/ui/control/geolocate_control.ts#L43-L55

1. showUserLocation

trueにすると、地図上にユーザーの現在位置を示すマーカー(点)が表示されます。

2. showAccuracyCircle

trueにすると、位置マーカーの周囲に、位置情報の精度を表す半透明の円が表示されます。

この円の大きさは、位置情報の取得精度(accuracy)に応じて動的に変化します。

3. positionOptions

Geolocation APIのgetCurrentPosition()メソッドに渡されるオプションです。詳細は先の節をご覧ください。

4. fitBoundsOptions

取得した位置情報に合わせて地図を移動する際の設定です。

3つの値があります。詳細はFitBoundsOptionsを参照してください。

  1. linear: trueの時は、直線的に移動します(easeTo)。falseの時は、弧を描くように移動します(flyTo)。デフォルトはfalse
  2. maxZoom: 最大ズームレベル。地図を拡大しすぎないように制限できます(後述
  3. offset: 新しい表示範囲の中心を、上下左右にピクセル単位でどれくらいずらすか。デフォルトは[0, 0]

5. trackUserLocation

trueにすると、「ユーザーが移動するたびに地図の中心を追従するモード」になります(後述)。

表示領域とズームレベル

GeolocateControlのボタンを押すと、ユーザーの位置情報が取得され、地図の表示領域がその場所へ自動的に移動します。

デフォルトでは、この移動の際に位置情報の取得精度(accuracy)を示す円がぴったりと収まるように、地図がズームインします。しかし、精度が高く数メートルから数十メートルの場合、そのエリアに寄り過ぎてしまい、周囲の状況が分かりづらくなってしまいます。

位置情報の取得精度(accuracy)を示す円がぴったりと収まる例

図: 位置情報の取得精度(accuracy)を示す円がぴったりと収まる例

これを防ぐには、fitBoundsOptionsのオプションにあるmaxZoomを設定します。この値を調整することで、精度が高くても地図が過度にズームインするのを防ぎ、適切な縮尺で表示させることができます。デフォルトではmaxZoomの値として「15」が設定されています。

他にはオプションで、オフセット(余白)のピクセル数を設定することもできます。

二つのモード - trackUserLocation オプション

GeolocateControlには、二つのモードがあります。これは、trackUserLocationオプションの値(boolean)によって切り替えることができます。

ノーマルモード (trackUserLocation: false

このモードでは、GeolocateControlは単なるボタンとして機能します。ボタンがクリックされると、一度だけユーザーの位置情報を取得し、その場所にマーカーを表示して地図を移動させます。ボタンの色は変化せず、ユーザーがその後移動しても地図は更新されません

ウォッチモード (trackUserLocation: true

このモードでは、GeolocateControlはトグルとして機能します。ボタンを押すと、ユーザーの位置情報を継続的に追跡し、その動きに合わせて地図もリアルタイムで更新されます。このとき、ボタンの色は青色に変わります。もう一度ボタンを押すと、この追跡機能は停止されます。

このモードには、3つの状態があります:

1. active (アクティブ): この状態では、地図のカメラをユーザーの位置変化に合わせて自動的に追従します。これにより、ユーザーの位置を示す点が常に地図の中央に表示され続けます。このモードは、GeolocateControlボタンを初めてクリックした時や、手動で追従を有効にした際に切り替わります。ナビゲーション中のような、常に現在地を中央に表示しておきたい場合に最適なモードです。

2. passive (パッシブ): この状態では、ユーザーの位置情報を示す点は自動で更新されますが、地図のカメラは追従しません。この状態は、ユーザーが地図をドラッグして手動で移動させた際に自動的に切り替わります。ユーザーが位置追跡を中断し、地図を自由に探索したい場合に適しています。

3. disabled (無効): この状態は、位置情報機能が利用できない、無効にされている、またはユーザーが位置情報の利用を拒否した場合に発生します。このモードでは、位置情報の追跡や表示は行われません。

内部的には OFF, ACTIVE_LOCK, WAITING_ACTIVE ACTIVE_ERROR, BACKGROUND, BACKGROUND_ERROR という状態が定義されています。

https://github.com/maplibre/maplibre-gl-js/blob/c437b82ea8a4f93b5cb9c3fce4f486ddb717951f/src/ui/control/geolocate_control.ts#L249-L265

GeolocateControl state diagram

イベント

Geolocatation APIから位置情報が取得されると geolocate イベントが発火されます。

また、もし地図範囲が限定されている時に、現在地がその範囲外だった際には代わりに outofmaxbounds イベントが発火されます。

余談: この記事を書くために調査していたとき、バグがあったのを発見しました。修正PRが取り込まれ、 v5.8.0 リリースに含まれています! Fix: Prevent error when GeolocateControl fires outofmaxbounds event with trackUserLocation disabled by sorami · Pull Request #6464 · maplibre/maplibre-gl-js

その他、ウォッチモードの際には、状態が変わる時に trackuserlocationstart, userlocationlostfocus などのイベントが発火されます(詳細は公式ドキュメントを参照ください)。

不具合が生じた際には error イベントが発火されます。

地図上で現在地を示すマーカー

現在地のドット(点)と、精度範囲を表す円は、「レイヤー」ではなく「マーカー」として描画されています。

それぞれ .maplibregl-user-location-dot, .maplibregl-user-location-accuracy-circle というクラス名で、スタイルが適用されています。

https://github.com/maplibre/maplibre-gl-js/blob/5e0f8f749eab9bb5e0ea7e0a34a14112b52f0a66/src/css/maplibre-gl.css#L765-L770

https://github.com/maplibre/maplibre-gl-js/blob/5e0f8f749eab9bb5e0ea7e0a34a14112b52f0a66/src/css/maplibre-gl.css#L809-L814

また、パルス(点滅)のアニメーションがCSSで実現されています。

https://github.com/maplibre/maplibre-gl-js/blob/5e0f8f749eab9bb5e0ea7e0a34a14112b52f0a66/src/css/maplibre-gl.css#L772-L799

ボタンのアイコン

GeolocateControlボタンのアイコンは、SVGファイルとして一つだけ存在し、これのスタイルを変更することで、それぞれの状態に応じた見た目になります。以下の図で、元のSVGと、バリエーションを列挙しました。

GeolocateCotrolのアイコン - バリエーション

maplibregl-ctrl-geolocate.svg

https://github.com/maplibre/maplibre-gl-js/blob/c437b82ea8a4f93b5cb9c3fce4f486ddb717951f/src/css/maplibre-gl.css#L324-L374

ウォッチモードの際には以下のCSSクラスが付与され、これを元にアイコンの見た目が変更されます。

  • .maplibregl-ctrl-geolocate-waiting - 取得中(青色 + 回転アニメーション)
  • .maplibregl-ctrl-geolocate-active - アクティブ(青色)
  • .maplibregl-ctrl-geolocate-active-error - アクティブ, エラー(赤色)
  • .maplibregl-ctrl-geolocate-background - バックグラウンド(青色 + 透明)
  • .maplibregl-ctrl-geolocate-background-error - バックグラウンド, エラー(赤色 + 透明)

https://github.com/maplibre/maplibre-gl-js/blob/c437b82ea8a4f93b5cb9c3fce4f486ddb717951f/src/css/maplibre-gl.css#L376-L443

ボタンのスタイル変更: 「れきちず」の例

わたしたちMIERUNEは、歴史的な地図を現代の地図感覚で楽しめるウェブサービス「れきちず」を運営しています。

https://rekichizu.jp/

この地図では、MapLibre GL JSのデフォルトボタンではなく、独自にデザインしたものを利用しています。

「れきちず」の地図コントロールボタン

これは現状では、コントロール要素へデフォルトで付与されるクラス名に対するスタイルを上書きすることで実現しています。見た目だけでなく、取得中のアニメーションも、デフォルト(回転)から点滅へ変更しています。

💡 れきちずでのCSSの例
/* 現在地 - デフォルト */
.maplibregl-ctrl-geolocate .maplibregl-ctrl-icon {
    background-image: url('/map-controls/geolocation.svg') !important;
	background-size: 46px 46px !important;
	background-position: center !important;
}

/* 現在地 - 状態共通スタイル */
.maplibregl-ctrl-geolocate-active .maplibregl-ctrl-icon,
.maplibregl-ctrl-geolocate-waiting .maplibregl-ctrl-icon,
.maplibregl-ctrl-geolocate-background .maplibregl-ctrl-icon,
.maplibregl-ctrl-geolocate-active-error .maplibregl-ctrl-icon,
.maplibregl-ctrl-geolocate-background-error .maplibregl-ctrl-icon {
	background-image: none !important;
	-webkit-mask-image: url('/map-controls/geolocation.svg') !important;
	mask-image: url('/map-controls/geolocation.svg') !important;
	-webkit-mask-size: 46px 46px !important;
	mask-size: 46px 46px !important;
	-webkit-mask-position: center !important;
	mask-position: center !important;
	-webkit-mask-repeat: no-repeat !important;
	mask-repeat: no-repeat !important;
}

/* 現在地 - 各状態の色 */
.maplibregl-ctrl-geolocate-waiting .maplibregl-ctrl-icon {
	background-color: #94a3b8 !important;
	animation: pulse 1.5s infinite !important;
}
.maplibregl-ctrl-geolocate-active .maplibregl-ctrl-icon {
	background-color: rgba(77, 159, 235, 1) !important;
}
.maplibregl-ctrl-geolocate-background .maplibregl-ctrl-icon {
	background-color: rgba(77, 159, 235, 0.6) !important;
}
.maplibregl-ctrl-geolocate-active-error .maplibregl-ctrl-icon {
	background-color: rgb(229, 77, 46, 1) !important;
}
.maplibregl-ctrl-geolocate-background-error .maplibregl-ctrl-icon {
	background-color: rgb(229, 77, 46, 0.6) !important;
}

/* パルスアニメーション */
@keyframes pulse {
	0%,
	100% {
		opacity: 1;
	}
	50% {
		opacity: 0.6;
	}
}

まとめ

この記事では、ウェブ地図を作るためのライブラリ「MapLibre GL JS」の現在地機能について、ベースとなるGeolocation APIから解説しました。

ウェブブラウザのGeolocation APIは、HTTPS環境でユーザーの許可を得て位置情報を取得します。getCurrentPosition()で一度だけ、watchPosition()で継続的な位置追跡することができます。

MapLibre GL JSのGeolocateControlを使えば、このAPIを意識することなく、簡単に現在地機能を地図に追加できます。「ノーマルモード」(一度だけ取得)と「ウォッチモード」(継続追跡)があり、ウォッチモードではユーザーの位置を追跡し、それにあわせて地図を更新します。

Enjoy mapping!

宣伝

MIERUNEは、オープンソースのコミュニティから生まれた、位置情報の会社です。

わたしたちは、MapLibreプロジェクトの世界で初めてのスポンサーとして、2022年から年間2万ドル(約300万円, 2025年10月現在)を支援しています。今ではAWS、Meta、Microsoftといった企業もスポンサーに名を連ねています。

https://opencollective.com/maplibre

オープンソースや位置情報についてご興味のある方、ぜひカジュアル面談でお話ししましょう!

https://welcome.mierune.co.jp/

MIERUNEのZennブログ

Discussion