Google ロゴ

visgl/react-google-maps を用いて、ブラウザで Google Map を表示する

2024/12/08

背景

ブラウザで Google Map を表示したいなと思い調べてみると、
Google Maps Platform が React インテグレーション ライブラリ 1.0 を発表
という記事を見つけました。

visgl/react-google-maps とは

visgl/react-google-mapsは React サポートの Google Map を提供するライブラリです。
v1.0.0 は 2024-05-11 にリリースされたようで、公式サイトの Exampleに実装例が載っているのですが、かなり色んな表現ができそうです。
geospatial data visualization frameworks を提供する vis.gl が管理しているようで、そのためパフォーマンスや開発体験、機能などが期待できそうです。

Google Cloud 設定

react-google-maps を使うためには Google Cloud の API キーが必要です。

  1. Google Cloud の API とサービスにアクセス
  2. 認証情報タブにアクセスし、認証情報を作成ボタンをクリックし、API キーを選択

GoogleMapAPIを有効

  1. 作成した API キーを選択し、名前や制限を設定
  2. 右上の API Key をあとで使うため保存しておく

GoogleMap�のTogello用のAPIキー

ライブラリをインストール

僕の場合は bun を使っているので、以下のコマンドでインストール。
※npm や yarn でもインストール可能なので、ご利用のパッケージマネージャーでインストールしてください。

bun install @vis.gl/react-google-maps

react-google-maps を利用

あくまでも僕の場合の実装例ですが、下記のような形で理想的な Google Map を表示できました。

import { APIProvider } from "@vis.gl/react-google-maps";
import {
  type MapCameraChangedEvent,
  Marker,
  Map as ReactGoogleMaps,
} from "@vis.gl/react-google-maps";
import { type FC, useCallback, useState } from "react";

type Props = {
  currentLocation: {
    lat: number;
    lng: number;
  };
};

export const GoogleMap: FC<Props> = ({ currentLocation }) => {
  const [currentCenter, setCurrentCenter] = useState(currentLocation);

  // 環境変数に「Google Cloud 設定」で取得したAPIキーを設定
  const GOOGLE_MAPS_API_KEY = process.env.GOOGLE_MAPS_API_KEY ?? "";

  const handleCenterChanged = useCallback((event: MapCameraChangedEvent) => {
    setCurrentCenter(event.detail.center);
  }, []);

  return (
    {/* 記事のわかりやすさ的にコンポーネント内にProviderを記述しましたが、自身のコードの適切な場所にProviderは記述してください */}
    <APIProvider apiKey={GOOGLE_MAPS_API_KEY}>
      <ReactGoogleMaps
        style={{ width: "100%", height: "300px" }} // 地図のサイズ
        defaultCenter={currentLocation} // 一番最初の中心座標
        center={currentCenter} // 中心座標。currentCenterが変われば中心座標も変わる
        defaultZoom={15} // 設定しないと世界地図が表示されちゃうので、近場が表示されるサイズに設定
        colorScheme="DARK" // ダークモードで表示
        reuseMaps // これをtrueにすると、地図がキャッシュされる
        disableDefaultUI // 無駄なUIを非表示
        onCenterChanged={handleCenterChanged} // ユーザー操作で中心座標が変わるようにする
      >
        {/* マーカーを表示 */}
        <Marker position={currentLocation} clickable={false} />
      </ReactGoogleMaps>
    </APIProvider>
  );
};

Geolocation API を利用

位置情報が既に決まっている場合は上記の実装の currentLocation に緯度と軽度を渡してあげれば良いですが、僕の場合はユーザーの現在地を表示したいため、位置情報を取得する必要がありました。
Geolocation APIというブラウザ標準の API を使うことで、ユーザーの現在地を取得することができます。

これもまた僕の場合の実装例ですが、下記のような形で実装しました。

import { useEffect, useState } from "react";

type UseGeolocation = {
  currentLocation: {
    lat: number;
    lng: number;
  };
  isNotSupportedGeolocation: boolean;
  isNotHasPermission: boolean;
};

export const useGeolocation = (): UseGeolocation => {
  const [currentLocation, setCurrentLocation] = useState({
    lat: 35.681236,
    lng: 139.767125,
  }); // 初期座標: 東京
  const [isNotSupportedGeolocation, setIsNotSupportedGeolocation] =
    useState(false);
  const [isNotHasPermission, setIsNotHasPermission] = useState(false);

  useEffect(() => {
    if (!navigator.geolocation) {
      setIsNotSupportedGeolocation(true);
      return;
    }

    let lastUpdateTime = 0; // 最後に更新された時間を記録

    const watchId = navigator.geolocation.watchPosition(
      (position) => {
        const currentTime = Date.now();
        if (currentTime - lastUpdateTime >= 10000) {
          // 最後の更新から10秒以上経過した場合のみ更新
          const { latitude, longitude } = position.coords;
          setCurrentLocation({ latitude, longitude });
          lastUpdateTime = currentTime;
        }
      },
      (error) => {
        console.error(error);
        setIsNotHasPermission(true);
      },
      {
        enableHighAccuracy: false, // 高精度な位置情報を取得しない
        timeout: Number.POSITIVE_INFINITY, // タイムアウトしない
        maximumAge: 1000 * 10, // 10秒以内の位置情報を取得
      }
    );

    return () => {
      navigator.geolocation.clearWatch(watchId); // コンポーネントのアンマウント時に監視を解除
    };
  }, []);

  return { currentLocation, isNotSupportedGeolocation, isNotHasPermission };
};

navigator.geolocation が undefined の場合はブラウザが対応していない可能性が高いです。
navigator.geolocation.xxx と呼び出すことで、ブラウザの機能で自動的にユーザーに位置情報の許可を求めるダイアログが表示されます。

navigator.geolocation.getCurrentPosition を利用することで一度だけ位置情報を取得することができますが、今回は navigator.geolocation.watchPosition を利用して端末の位置が変化するたびに位置情報を取得するようにしました。

位置情報をバックエンドで保存をしている形にしたかったので、端末の位置が変わるたびに API が呼び出されると自分自身で Dos 攻撃を実装しているような形になってしまうため、最後の更新から 10 秒以上経過した場合のみ位置情報を更新するようにしました。

最後に

これでブラウザでユーザーの現在地を取得し、Google Map にその位置を表示することができました。
こんなにも簡単に Google Map の表示ができるとは思っていなかったので、react-google-maps に感謝です。

Togello設定ページで位置情報の表示

Twitterフォロー待ってます!