@react-google-maps/apiを使った地図アプリでマーカーとバルーンの位置関係をいい感じに表示する

2021.02.16

こんにちは、CX事業本部の若槻です。

前回の記事では@react-google-maps/apiの基本的な動作をReactアプリを実際に起動して確認しました。

今回は、@react-google-maps/apiを使った地図アプリでマーカーとバルーンの位置関係をいい感じに表示する方法を確認してみました。

やりたいこと

下記のように指定の座標位置に配置されたマーカーの上部にバルーンを表示し、マップの拡縮をしてもマーカーとバルーンの相対位置が変わらないようにしたいです。

やってみる

環境作成

前回の下記記事で作成した環境があればそのまま利用可能です。

未作成の場合は下記コマンドを実行してReactプロジェクト作成とパッケージインストールを行います。

% npx create-react-app react-google-maps-api-local --typescript
% cd react-google-maps-api-local
% npm i @react-google-maps/api
% npm i

APIキーの用意

「Maps JavaScript API」を利用可能なGoogle Maps PlatformのAPIキーを用意します。まだの場合は下記を参考に作成してください。

@types/googlemaps導入

下記コマンドを実行して@types/googlemapsをインストールします。

% npm i @types/googlemaps --save-dev

これによりgoogle.maps.Size()の型エラーを抑制できます。

App.tsx編集

src/App.tsxを下記のコードに変更します。

src/App.tsx

import { useState } from "react";
import {
  GoogleMap,
  LoadScript,
  InfoWindow,
  Marker,
} from "@react-google-maps/api";

const containerStyle = {
  height: "100vh",
  width: "100%",
};

const center = {
  lat: 35.69575,
  lng: 139.77521,
};

const positionAkiba = {
  lat: 35.69731,
  lng: 139.7747,
};

const positionIwamotocho = {
  lat: 35.69397,
  lng: 139.7762,
};

const divStyle = {
  background: "white",
  fontSize: 7.5,
};

const MyComponent = () => {
  const [size, setSize] = useState<undefined | google.maps.Size>(undefined);
  const infoWindowOptions = {
    pixelOffset: size,
  };
  const createOffsetSize = () => {
    return setSize(new window.google.maps.Size(0, -45));
  };
  return (
    <LoadScript googleMapsApiKey="API_KEY" onLoad={() => createOffsetSize()}>
      <GoogleMap mapContainerStyle={containerStyle} center={center} zoom={17}>
        <Marker position={positionAkiba} />
        <Marker position={positionIwamotocho} />
        <InfoWindow position={positionAkiba} options={infoWindowOptions}>
          <div style={divStyle}>
            <h1>秋葉原オフィス</h1>
          </div>
        </InfoWindow>
        <InfoWindow position={positionIwamotocho} options={infoWindowOptions}>
          <div style={divStyle}>
            <h1>岩本町オフィス</h1>
          </div>
        </InfoWindow>
      </GoogleMap>
    </LoadScript>
  );
};

export default MyComponent;

マーカーMarkerとバルーンInfoWindowは別々のオブジェクトです。<InfoWindow>positionで指定した座標位置からの画面上の絶対距離をoptionspixelOffsetにより定義することにより、マーカーの上部にバルーンが常に表示されるようにしています。

アプリの起動

Reactアプリを起動します。

% npm run start

マップ上でマーカーとバルーンの位置関係をいい感じに表示できていますね。

useStateをなぜ使う必要があるか

今回のコードでは<InfoWindow>optionspixelOffsetの値を直接指定せず、<LoadScript>onLoadオプションのコールバック関数の中でuseStateにより作成した変数を介して指定しています。なぜこんな遠回りなことをしているかと言うと、window.google.maps.Size()は画面上のマップウィンドウ上での絶対距離をもとにSizeオブジェクトを作成するため、マップの読み込み前に実行されるとエラーとなるからです。

よって下記の様にpixelOffsetの値をuseStateを使わず直接指定しようとすると、

src/App2.tsx

const MyComponent = () => {
  const infoWindowOptions = {
    pixelOffset: new window.google.maps.Size(0, -45),
  };
  return (
    <LoadScript googleMapsApiKey="API_KEY">
      <GoogleMap mapContainerStyle={containerStyle} center={center} zoom={17}>
        <Marker position={positionAkiba} />
        <Marker position={positionIwamotocho} />
        <InfoWindow position={positionAkiba} options={infoWindowOptions}>
          <div style={divStyle}>
            <h1>秋葉原オフィス</h1>
          </div>
        </InfoWindow>
        <InfoWindow position={positionIwamotocho} options={infoWindowOptions}>
          <div style={divStyle}>
            <h1>岩本町オフィス</h1>
          </div>
        </InfoWindow>
      </GoogleMap>
    </LoadScript>
  );
};

TypeError: Cannot read property 'maps' of undefinedという実行時エラーとなります。

よって、onLoadでマップの読み込みが完了した上でpixelOffsetの値をuseStateにより作成する必要がありました。

おわりに

@react-google-maps/apiを使った地図アプリでマーカーとバルーンの位置関係をいい感じに表示する方法を確認してみました。

React Hooksの仕様の理解が曖昧だったのでちゃんと理解するのに苦労しましたが、今回の確認を通じて理解が深められて良かったです。

参考

以上