🍣

Nextのページで、react-google-maps/apiを利用して、オーバーレイマップを作る

2021/12/15に公開

例によって、今回も備忘録です。

まず完成したものは、こちら

https://historedge.com/map/hachinohe/

このようなネイティブアプリを作ろうと思い地図を作りましたが、データを一回載せてみてみたくて、自分のサイトに乗っけてみました。
ローカルでの実装はテンプレ通りすぐできます。が、ビルドするためにエラーを消すのに一苦労でした。

環境

Next.js, TypeScriptのサイト

Google Cloud PlatformでAPIを発行する

GCPでMaps JavaScript APIを発行し、Keyを.envに入れる

NEXT_PUBLIC_GOOGLE_MAP_API_KEY=コピったあなたのAPI Key

react-google-mapsで、実装したいモデルを探す

https://react-google-maps-api-docs.netlify.app/
イントロダクションを見て、インストール可能です。
Reactのバージョンが新しいと使えるコンポネントが違うので、一応読みましょう。
さっきコピったAPIKeyを入れれば、サンプルを見れます。

今回は、古地図のデータをSVGで用意し、マップ上に張り付けて透過させるアプリを作るので、Ground Overlayを使用します。
https://react-google-maps-api-docs.netlify.app/#groundoverlay

Overlayを入れてみる

まずinstall
yarn add @react-google-maps/api
Componentを作りましょう

index.tsx
import React from 'react'
import { GoogleMap, LoadScriptNext, GroundOverlay } from '@react-google-maps/api'

const key = process.env.NEXT_PUBLIC_GOOGLE_MAP_API_KEY

// Google Mapを描画するサイズ
const containerStyle = {
  width: '90vw',
  height: '58vh',
}
// マップの中央(Google Mapでポイントすれば出てくる)
const center = {
  lat: 40.513103, // 緯度
  lng: 141.4897, // 経度
}
// オーバーレイする画像の右上と左下の点
const bounds = {
  north: 40.52,
  south: 40.4942,
  east: 141.513105,
  west: 141.4666
}

const OverlayMap = () => {
  return (
      <LoadScriptNext googleMapsApiKey={key}>
        <GoogleMap
          mapContainerStyle={containerStyle}
          center={center}
          zoom={16}
	  // マップのスタイル
          options={{
            gestureHandling: 'greedy',
            streetViewControl: false,
            fullscreenControl: false,
          }}
        >
          <GroundOverlay
          key={'url'}
          url="載せたい画像のURL"
          bounds={bounds}
          opacity={0.5}
        />
        </GoogleMap>
      </LoadScriptNext>
  )
}

おそらくこの時点で、localhostではオーバーレイが反映されてると思います。
ただboundにエラーが出てると思います。
とりあえず、無視して、地図の不透明度をスライダーで変える処理をしましょう。

react slider componentをinstallして、onChangeで<Overlay>に不透明度の値を渡す

yarn add @mui/material/Box @mui/material/Slider
index.tsx
import Box from '@mui/material/Box';
import Slider from '@mui/material/Slider';

const OverlayMap = () => {
 const [inputValue, setInputValue] = React.useState(0.5)
  const handleChange = (e) => {
    setInputValue(e.target.value)
  }
  return (
     <>
      <LoadScriptNext googleMapsApiKey={key}>
	<GoogleMap>
	  <GroundOverlay
          key={'url'}
          url="載せたい画像のURL"
          bounds={bounds}
          opacity={inputValue}
        />
	</GoogleMap>
      </LoadScriptNext>
      <Box sx={{ width: '80vw' }}>
        <Slider
          aria-label="opacity"
          defaultValue={0.5}
          step={0.1}
          marks
          min={0.0}
          max={1.0}
          onChange={(e) => handleChange(e)}
        />
      </Box>
    </>
  )
}

イベントハンドラーは、こちらを参考にしました。
https://zenn.dev/nbr41to/articles/3f1ae8cbc532b6

これで、不透明度をスライダーで変更できたと思います。
では、ビルドするために、boundsのエラーを直しましょう。

boundsはgoogle.maps.LatLngとする

index.tsx
-const bounds = {
-  north: 40.52,
-  south: 40.4942,
-  east: 141.513105,
-  west: 141.4666
-}
+  const sw = new google.maps.LatLng(40.4942, 141.4666)
+  const ne = new google.maps.LatLng(40.52, 141.513105)
+  const bounds = new google.maps.LatLngBounds(sw, ne)

するとエラーメッセは消えました。
ではyarn buildしてみましょう。

google is not definedを解決する

あれ、、、

調べると、new google.map.LatLng()ではなく、
new window.google.map.LatLng()
としなきゃならないらしいです。
https://stackoverflow.com/questions/50548632/react-google-maps-google-is-not-defined-error

そしたらなんと、windowがnot definedと言われました。

これは、NextがSSRであって、サーバーサイドで処理するときに、windowというブラウザ側にしかないグローバルオブジェクトを参照しようとするからエラーが起こるようです。
なので、windowがundefinedであるときの処理をifで括ってしまいます。
https://qiita.com/ku1987/items/e592cb5133659c3136de

window is not definedを解決

windowを用いる処理は全てif内に入れます。

index.tsx
const OverlayMap = () => {
  const [inputValue, setInputValue] = React.useState(0.5)
  const handleChange = (e) => {
  }
  const OverlayData = () => {
    if (typeof window !== 'undefined') {
      const sw = new window.google.maps.LatLng(40.4942, 141.4666)
      const ne = new window.google.maps.LatLng(40.52, 141.513105)
      const bounds = new window.google.maps.LatLngBounds(sw, ne)
      return (
        <GroundOverlay
          key={'url'}
          url="載せたい画像のURL"
          bounds={bounds}
          opacity={inputValue}
        />
      )
    }
  }
  return (
    <div>
      <LoadScriptNext googleMapsApiKey={key}>
        <GoogleMap>
          <OverlayData />
        </GoogleMap>
      </LoadScriptNext>
      <p>古地図不透明度</p>
      <Box>
      </Box>
    </div>
  )
}	

このように、関数の中に、windowのif文を定義した関数を入れ込みます。
<GroundOverlay>もwindowを参照するので、ifのreturnで返して、<OverlayData />として本文のJSXに入れます。

完成

これでyarn buildすれば、無事doneすると思います!
ただ、react-google-mapsは4年前から更新されてないので、ちゃんとした製品にするなら、違う方法のほうが無難かも。
githubはこちら。
https://github.com/renshimosawa/portfolio/tree/main

Discussion