Next.js + Deck.gl + Google Photorealistic 3D Tilesで3D地図アプリの開発を始める

![スクリーンショット 2023-10-03 22.38.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2661777/35bd87b1-1e0e-8602-bad2-e12ff16e465c.jpeg)
Next.jsとDeck.glでGoogleのPhotorealistic 3D Tilesを利用するためのハウツーです。カバー画像のような3D地図をブラウザで表示できるようにするところまで解説します。

[Next.js Foundations Course](https://nextjs.org/learn/foundations/about-nextjs)を参照しながら実装したため、備忘録をかねて書き残しておきます。

## Map Tiles APIを有効にする
Photorealistic 3D Tilesを利用するには、Map Tiles APIが有効にになっている必要があります。まずは、以下のGoogleのドキュメントに従ってAPIを有効にして、APIキーを取得ししてください。のちほどDeck.glから3D Tileを呼び出す際にAPIキーを利用します。

https://developers.google.com/maps/documentation/tile/overview?hl=ja

## Next.js アプリの作成
```shell
npx create-next-app@latest deck-demo --use-npm
```

```shell
✔ Would you like to use TypeScript? … Yes
✔ Would you like to use `src/` directory? … No
✔ Would you like to use App Router? (recommended) … No
```

アプリケーションのディレクトリを作成する際にいくつかの質問がありますが、今回の記事は上記の設定で行っています。App RouterをNoにすると、Page Routerを利用するプロジェクトが作成されます。

:::note info
**App Router vs Pages Router**
現行のNext.jsでは、App RouterとPages Routerという２つのルーターが存在していて、プロジェクト作成時に選択します。Pages Routerと、App Routerではディレクトリ構成が異なるため、以下の記事ではPage Routerを選択した場合について記述しています。App Routerの方がバージョンとしては新しく推奨(recommended)となっていますが、今回は初学者としてPages Routerから使い方を理解したかったため、このような選択をしています。
:::


```shell
cd deck-demo
mkdir components
```
アプリケーションのディレクトリに移動して、componentsディレクトリを作成しておきます。

```shell
npm install deck.gl
```
必要なライブラリをインストールしましょう。

## 実装
index.tsx、deck-map.tsx、deck-map.module.cssの３つのファイルを作成していきます。

```
root/
　├ pages/
　│　└ index.tsx
　└ components/
　　　├ deck-map.tsx
　　　└ deck-map.module.css
```

### pages/

pages以下にindex.tsxやhoge.tsxのようなファイルを作成することで、ドメイン名/hogeでルーティングされアクセスできるようになります。

pages/index.tsxはすでに初期設定のページが存在しているので、以下のように書き換えます。

```TSX:pages/index.tsx
import Head from 'next/head'
import dynamic from 'next/dynamic';
const DeckMap = dynamic(() => import('../components/deck-map'), {
  ssr: false
});

export default function Home() {
  return (
  <>
    <Head><title>deck.gl Photorealistic 3D Tiles example</title></Head>
    <DeckMap />
  </>
  );
}

```
:::note warn
Deck.glの記述はindexとは別のコンポーネントとして作成して、Dynamic Importを使って読み込む必要があるので注意。SSR(サーバーサイドレンダリング)に失敗してエラーがでるの回避するため、Dynamic Importを使用して、ssr: falseの状態でインポートしています。

https://github.com/visgl/deck.gl/issues/7735
:::


### components/
ルーティングしたくない（ユーザーが直接アクセスしない）ページは、componentsディレクトリ以下に作成します。

ここではDeck.glのドキュメントより[Using deck.gl with React](https://deck.gl/docs/get-started/using-with-react
)、Googleのドキュメントより[3D タイル レンダラを使用する](https://developers.google.com/maps/documentation/tile/use-renderer?hl=ja#work_with_deckgl
)をそれぞれ参照して記述しています。

https://deck.gl/docs/get-started/using-with-react

https://developers.google.com/maps/documentation/tile/use-renderer?hl=ja#work_with_deckgl

```TSX:components/deck-map.tsx
import React, { useState } from 'react';
import DeckGL from '@deck.gl/react/typed';
import {Tile3DLayer} from '@deck.gl/geo-layers/typed';
import styles from './deck-map.module.css';

const GOOGLE_API_KEY = `YOUR_API_KEY_HERE`;
const TILESET_URL = `https://tile.googleapis.com/v1/3dtiles/root.json`;

// Viewport settings
const INITIAL_VIEW_STATE = {
  longitude: 139.695398,
  latitude: 35.68853,
  zoom: 16,
  bearing: 90,
  pitch: 70,
  height: 200
};

// DeckGL react component
export default function App() {
  const [credits, setCredits] = useState(new Set());

  const layers = [
  new Tile3DLayer({
   id: 'google-3d-tiles',
   data: TILESET_URL,
   loadOptions: {
    fetch: {
      headers: {
        'X-GOOG-API-KEY': GOOGLE_API_KEY
      }
    }
  },
  onTilesetLoad: (tileset3d) => {
    tileset3d.options.onTraversalComplete = (selectedTiles) => {
      const credits = new Set();
      selectedTiles.forEach((tile) => {
        const { copyright } = tile.content.gltf.asset;
        copyright.split(';').forEach(credits.add, credits);
      });
      setCredits(credits);
      return selectedTiles;
    };
  }
  })
  ];

  return (
    <div>
      <div className={styles.map}>
        <DeckGL initialViewState={INITIAL_VIEW_STATE} controller={true} layers={layers} />
      </div>
      <div className={styles.credits}>{Array.from(credits).join('; ')}</div>
    </div>
);
}


```
:::note warn
GOOGLE_API_KEYは、前項で取得したAPIキーに置き換えてください。ここでは、ハードコーディングしていますが、next.jsでは.env.localに環境変数を定義します。
:::

:::note warn
Deck.glのパッケージのインポートは、.jsxの場合と、.tsxの場合で異なります。TypeScriptでコードを記述する場合、`@deck.gl/module_name/typed`となります。
https://deck.gl/docs/get-started/using-with-typescript


* .jsx
import DeckGL from '@deck.gl/react';
import {GeoJsonLayer} from '@deck.gl/layers';

* .tsx
import DeckGL from '@deck.gl/react/typed';
import {GeoJsonLayer} from '@deck.gl/layers/typed';
:::
``` css:components/deck-map.module.css
.map {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 100%;
}

.credits {
  position: absolute;
  bottom: 0;
  right: 0;
  padding: 2px;
  font-size: 15px;
  color: white;
  text-shadow: -1px 0 black, 0 1px black, 1px 0 black, 0 -1px black;
}
```
## ローカルサーバーの起動
アプリのルートディレクトリで以下のコマンドを実行します。
```shell
npm run dev
```

[http://localhost:3000](http://localhost:3000)にアクセスして、以下のように3D地図が表示されたら成功です。お疲れ様でした。

![スクリーンショット 2023-10-13 12.43.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2661777/22284235-f271-bb17-c181-a56b2def721a.png)