tpc06-2

TOPIC 6|Cesiumで活用する[2/2]|Reactを用いたCesiumの応用

ReactはWebサイト・WebアプリのUI構築用に設計されたJavaScriptライブラリです。ここでは、ReactとNext.jsを用いて、PLATEAUの3D都市モデルを宣言的に扱う方法を解説し、作例としてPLATEAUの3D都市モデルをCesium上で表示します。Cesiumの活用事例も紹介しています。

Share

【目次】

6.3  Reactを用いたCesiumの応用

 6.3.1  Cesiumを宣言的に扱う

 6.3.2  Cesiumで白い地球を表示する

 6.3.3  3D都市モデルの表示

 6.3.4  表現の調整

 6.3.5  地形の調整

 6.3.6  地図の表示

6.4  事例

6.3 _ Reactを用いたCesiumの応用

このトピックでは、ReactとNext.jsを用いて、PLATEAUの3D都市モデルを宣言的に扱う方法を紹介します。動作環境は、次のとおりです(すべて執筆時2022年8月時点での最新)。

Cesium v1.98

React v18

Next.js v12

TypeScript v4.8

6.3.1 _ Cesiumを宣言的に扱う

Reactは、宣言的プログラミングのJavaScriptライブラリです。HTMLの差分検出処理(Reconciler)を備えていますが、HTMLに限らず汎用的な宣言的プログラミングのライブラリとして用いることができます。

宣言的(Declarative)であることは、複雑なインタラクションや高度なユーザーインタフェースの構築を容易にします。それにあたっては、副作用を排したひとかたまりの処理(= コンポーネント)を定義し、それらの組み合わせを記述します。

■ 逐次的な例

例として「6.2 CesiumJSの基礎を体験する」から抜粋した次の処理を考えます(例示に無関係な箇所は「...」で省略しています)。

import { Cesium3DTileset, Viewer } from 'cesium'
 
// 1. Viewerを作成する
const viewer = new Viewer(document.getElementById('root'))
// 3. tilesetをviewerに追加する
const tileset = viewer.scene.primitives.add(
  // 2. Cesium3DTilesetを作成する
  new Cesium3DTileset({
    url: '...'
  })
)
// 4. viewerのカメラ位置と角度をtilesetに合わせて移動する
viewer.flyTo(tileset)

これは以下の処理を逐次的(Imperative)に実行します。

1.Viewer(viewer)を作成する

2.Cesium3DTileset(tileset)を作成する

3.tilesetをviewerに追加する

4.viewerのカメラの位置と角度をtilesetに合わせてアニメーションする

これらの処理には、少なくとも3つの副作用があります。

● IDがrootのHTML要素に対応したViewer(viewer)の状態

● viewerに追加されたtilesetの状態

● tilesetへ移動したviewerのカメラの状態

■ 宣言的な例

Reactではこの一連の処理を、コンポーネントを用いて宣言的に記述します。どのようなコンポーネントの再利用を考慮するかによって設計は変わりますが、例えば 1.を行うコンポーネントViewer、2. 3. 4.を行うコンポーネントTilesetの 2 つを定義すると、以下のような状態を記述することになります(flyToの扱いに拡張性がありませんが、逐次的な例と対応するためにこのように定義しています)。

<Viewer id='root'>
  <Tileset url={A} flyTo />
</Viewer>

これは処理の順序ではなく、その構造と入力値のプロパティといった状態を記述したものです。このようにコンポーネント化された処理は、以下のように要素を増やすことも容易にできます。

<Viewer id='root'>
  <Tileset url={A} flyTo />
  <Tileset url={B} />
  <Tileset url={C} />
</Viewer>

また、潜在的にFast Refreshに対応することができます。プログラムを実行中に、状態の記述を以下のように変更してファイルを保存すると、ブラウザのリロードやその他の状態の初期化を伴わずにAからBへカメラが移動します。

<Viewer id='root'>
  <Tileset url={A} />
  <Tileset url={B} flyTo />
  <Tileset url={C} />
</Viewer>

ここからは、実際にPLATEAUの3D都市モデルを表示するプログラムを作る前に、Reactを用いてCesiumを宣言的に扱うための考え方を例示します。

逐次的な書き方では数行のコードも、宣言的な書き方にすると、それまで考慮されていなかった(しかしながら考慮すべきだった)処理を考える必要が出てきます。

以下に提示するのは発展的な内容なので、必要に応じてReactとCesiumの公式ドキュメント、チュートリアルやリファレンスを参照してください。特にReactのフックの導入以降のドキュメンテーションは、以下で例示するコードを理解するのに必要です。

Reactのドキュメンテーション

Reactのチュートリアル

Cesiumのチュートリアル

Cesiumのクラスリファレンス

■ Viewerコンポーネントの作成

まず、「1. Viewer(viewer)を作成する」を行うコンポーネントViewerを作ります。入力は、CesiumのViewerコンストラクタに与えるHTMLElementのID(id)と子ノード(children)です。

コンポーネントは原則として、生存期間を越えた副作用を残してはいけません。1.には作成したViewerインスタンスを破壊する処理「Viewer#destroy」が必要です。逐次的な例では破棄について考慮しておらず、その動作は未定義です。しかしながら、入力をidとchildrenと定義すると、idが変更されたときにViewerインスタンスを破壊する処理が必要です。

import { Viewer as CesiumViewer } from 'cesium'
import React, {
  ReactNode,
  createContext,
  useLayoutEffect,
  useState
} from 'react'

export const ViewerContext = createContext<CesiumViewer | undefined>(undefined)

export const Viewer: React.FC<{
  id: string
  children: ReactNode
}> = ({ id, children }) => {
  const [viewer, setViewer] = useState<CesiumViewer>()

  // `id`が変更されるごとに実行される処理
  useLayoutEffect(() => {
    const element = document.getElementById(id)
    if (element == null) {
      // `element`がnullである場合は無視します。
      setViewer(undefined)
      return
    }
    const viewer = new CesiumViewer(element)
    setViewer(viewer)
    // useEffectあるいはuseLayoutEffectの返り値の関数は、次に`element`が変更されるご
    // とに、第一引数の関数より前に実行されます。
    // ここではViewerを破壊し、副作用を消去します。
    return () => {
      viewer.destroy()
    }
  }, [id])

  return (
    <ViewerContext.Provider value={viewer}>{children}</ViewerContext.Provider>
  )
}

なお上記のコードでは、Viewerコンポーネントが作成するViewerインスタンスは、ViewerContextコンテキストを用いて子孫ノードから参照できるようにするため、ViewerContextプロバイダの値に設定しています。

■ Tilesetコンポーネントの作成

次に、2. ~4.を行うコンポーネントTilesetを作ります。入力は、Cesium3DTilesetに与えるurlと、Viewer#flyToを呼び出すかどうかを示すflyToです(繰り返しになりますが、このflyToの扱いは逐次的な例と対応することが目的です)。

import { Cesium3DTileset } from 'cesium'
import React, { useContext, useEffect, useState } from 'react'

import { ViewerContext } from './Viewer'

export const Tileset: React.FC<{
  url: string
  flyTo?: boolean
}> = ({ url, flyTo = false }) => {
  const [tileset, setTileset] = useState<Cesium3DTileset>()
  const viewer = useContext(ViewerContext)

  // `url`あるいは`viewer`が変更されるごとに実行される処理
  useEffect(() => {
    if (viewer?.isDestroyed() !== false) {
      // `viewer`がnullであるか、既に破壊されている場合は無視します。
      setTileset(undefined)
      return
    }
    const tileset = new Cesium3DTileset({ url })
    viewer.scene.primitives.add(tileset)
    setTileset(tileset)
    return () => {
      // 上でaddしたCesium3DTilesetをremoveし、副作用を回収します。
      // Reactのコンポーネントは親から子の順で破壊されます。つまり、Tilesetが破壊され
      // るときには、既にViewerが破壊されている場合があるため、ここでもisDestroyedの
      // 確認が必要です。
      if (!viewer.isDestroyed()) {
        viewer.scene.primitives.remove(tileset)
      }
    }
  }, [url, viewer])

  // `flyTo`がtrueである場合にカメラを移動する。
  useEffect(() => {
    if (flyTo && viewer != null && tileset != null) {
      void viewer.flyTo(tileset)
    }
  }, [flyTo, viewer, tileset])

  return null
}

6.3.2 _ Cesiumで白い地球を表示する

以上の考え方を踏まえ、PLATEAUの3D都市モデルをCesium上で表示してみましょう。本解説ではブラウザ上で次の表示結果を得ることをゴールとします。

図6-11 ブラウザ上でこのような表示結果を得ることをゴールとする

■ 環境構築

プロジェクト用のディレクトリを作り、次の内容のpackage.jsonファイルを作成します。これは本チュートリアル内で使用しているNPMパッケージへの依存関係の記述です。

{
  "private": true,
  "dependencies": {
    "@emotion/react": "^11.10.4",
    "@emotion/styled": "^11.10.4",
    "@types/react": "^18.0.21",
    "@types/react-dom": "^18.0.6",
    "cesium": "^1.98.1",
    "next": "^12.3.1",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-merge-refs": "^2.0.1"
  },
  "devDependencies": {
    "@babel/core": "^7.19.3",
    "@types/node": "^18.8.3",
    "typescript": "^4.8.4"
  }
}

リスト6-5 package.json

リスト6-5を配置したディレクトリをカレントディレクトリとし、次のシェルコマンドを実行し、パッケージ群をインストールします。パッケージ群は、node_modulesディレクトリ内に保存されます。

npm install

CesiumはWeb Workerを多用しており、ブラウザ上での実行時にWeb Worker用のスクリプトを読み込めるように、Cesiumのファイルをホストする必要があります。ここでは、Next.jsの静的ファイル配信が有効になるpublicディレクトリ内にcesiumという名前でnode_modules/cesium/Build/Cesiumへのシンボリックリンクを作成します。

mkdir public
ln -s ../node_modules/cesium/Build/Cesium public/cesium

また、Next.jsのルーティングのためのpagesディレクトリを作成します。

mkdir pages

以上でセットアップは完了です。次のシェルコマンドを実行して開発サーバーを立ち上げます。

next dev

実行したら、ブラウザからhttp://localhost:3000を開いてください。「404: This page could not be found.」が表示されれば、環境構築は完了です。

【メモ】

ポート3000番が利用中の場合は、別のポートが使われることがあります。next devを実行したときに表示されるメッセージを確認してください。

以降では次のディレクトリ構成を想定します。pagesとsrcディレクトリを作成し、必要なファイルを都度作成してください。next-env.d.tsとtsconfig.jsonはNext.jsによって自動生成されます。

├── node_modules/
├── pages/
├── public/
├── src/
├── next-env.d.ts
├── package-lock.json
├── package.json
└── tsconfig.json

■ Viewerとページの作成

前述の「■Viewerコンポーネントの作成」と同様に、CesiumのViewerを扱うViewerコンポーネントを作成します。より汎用的にするために、HTMLElementのIDを入力するのではなく、コンポーネント内でHTMLDivElementを作成するようにします。

以下記載している見本のコードでは、Cesiumに組み込まれているすべての操作系UIとデフォルトの描画の大半を削除しています。

import { Global, css } from '@emotion/react'
import styled from '@emotion/styled'
import { Viewer as CesiumViewer, Color } from 'cesium'
import React, {
  ComponentPropsWithRef,
  ForwardedRef,
  createContext,
  forwardRef,
  useEffect,
  useLayoutEffect,
  useRef,
  useState
} from 'react'
import { mergeRefs } from 'react-merge-refs'
 
const useIsomorphicLayoutEffect =
  typeof window !== 'undefined' ? useLayoutEffect : useEffect
 
export const ViewerContext = createContext<CesiumViewer | undefined>(undefined)
 
const Root = styled.div(css`
  position: relative;
  width: 100%;
  height: 100%;
`)
 
export interface ViewerProps extends ComponentPropsWithRef<typeof Root> {
  viewerRef?: ForwardedRef<CesiumViewer>
}
 
export const Viewer = forwardRef<HTMLDivElement, ViewerProps>(
  ({ viewerRef, children, ...props }, forwardedRef) => {
    const ref = useRef<HTMLDivElement>(null)
    const [viewer, setViewer] = useState<CesiumViewer>()
    useIsomorphicLayoutEffect(() => {
      if (ref.current == null) {
        setViewer(undefined)
        return
      }
      const viewer = new CesiumViewer(ref.current, {
        // 今回は使用しないため、すべての組み込みUIを非表示にします。
        animation: false,
        baseLayerPicker: false,
        fullscreenButton: false,
        geocoder: false,
        homeButton: false,
        infoBox: false,
        sceneModePicker: false,
        selectionIndicator: false,
        timeline: false,
        navigationHelpButton: false,
        navigationInstructionsInitiallyVisible: false
      })
 
      // Viewerのピクセル密度をデバイスのピクセル密度に合わせます。
      // 注意:レンダリング品質は向上しますが、GPUへの負荷は大きくなります。
      viewer.resolutionScale = window.devicePixelRatio
 
      // 今回は使用しないため、組み込みのImagery Layerを削除します。
      viewer.scene.imageryLayers.removeAll()
 
      // 視覚的なパラメータ調整です。
      const scene = viewer.scene
      scene.skyBox = undefined as any
      scene.globe.baseColor = Color.WHITE
 
      // デフォルトでは地形に対してデプステストが行われません(地面にめり込んでいる建物
      // やその部分が表示される)。PLATEAUの3D都市モデルを用いる場合には基本的にtrueに
      // することになるでしょう。
      scene.globe.depthTestAgainstTerrain = true
 
      setViewer(viewer)
      return () => {
        viewer.destroy()
      }
    }, [])
 
    // コンポーネント外から`viewerRef`経由でViewerオブジェクトを利用可能にします。
    useEffect(() => {
      if (typeof viewerRef === 'function') {
        viewerRef(viewer ?? null)
      } else if (viewerRef != null) {
        viewerRef.current = viewer ?? null
      }
    }, [viewerRef, viewer])
 
    return (
      <Root ref={mergeRefs([ref, forwardedRef])} {...props}>
        <Global
          // Viewerが作成するHTMLCanvasElementを`Root`を覆うサイズまで広げます。
          styles={css`
            .cesium-viewer,
            .cesium-viewer-cesiumWidgetContainer,
            .cesium-widget,
            .cesium-widget canvas {
              width: 100%;
              height: 100%;
            }
          `}
        />
        <ViewerContext.Provider value={viewer}>
          {children}
        </ViewerContext.Provider>
      </Root>
    )
  }
)

リスト6-6 src/Viewer.tsx

次に、簡単にするため、ひとまずこのViewerだけを配置したAppコンポーネントを作成します。以降ではこのコンポーネントを随時編集していきます。

import React from 'react'

import { Viewer } from './Viewer'

export const App: React.FC = () => {
  return <Viewer />
}

リスト6-7 src/App.tsx

最後に、Appを配置したインデックスページを作成します。

import { Global, css } from '@emotion/react'
import { NextPage } from 'next'
import React from 'react'

import { App } from '../src/App'

// Cesiumが実行時にWeb Workerのためのスクリプトを読み込むために、「6.3.2 環境構築」
// で作成したpublic/cesiumの静的ファイルへのパスを指定します。
if (typeof window !== 'undefined') {
  window.CESIUM_BASE_URL = '/cesium'
}

const Index: NextPage = () => {
  return (
    <>
      <Global
        styles={css`
          html,
          body,
          #__next {
            width: 100%;
            height: 100%;
            margin: 0;
          }
        `}
      />
      <App />
    </>
  )
}

export default Index

リスト6-8 pages/index.tsx

以上でCesiumのViewerを表示するページを作成しました。http://localhost:3000をブラウザ上で開くと、次のように白い地球が表示されます。

図6-12 実行結果

6.3.3 _ 3D都市モデルの表示

■ PlateauTilesetコンポーネントの作成

■Tilesetコンポーネントの作成」と同様の手順で、3D Tiles形式のデータの読み込みと表示をするPlateauTilesetコンポーネントを作成します。ここではPLATEAU配信サービス(試験運用)を用います。入力はPLATEAU配信サービス(試験運用)のURLの一部pathと、建物の色colorです。

Cesium3DTileStyleのコンストラクタに渡すパラメータは、3D Tiles Styling言語という3D Tiles独自のフォーマットで指定します。

import { Cesium3DTileStyle, Cesium3DTileset } from 'cesium'
import React, { useContext, useEffect, useState } from 'react'
 
import { ViewerContext } from './Viewer'
 
export interface PlateauTilesetProps {
  path: string
  color?: string
}
 
export const PlateauTileset: React.FC<PlateauTilesetProps> = ({
  path,
  color = '#ffffff'
}) => {
  const [tileset, setTileset] = useState<Cesium3DTileset>()
  const viewer = useContext(ViewerContext)
  useEffect(() => {
    if (viewer?.isDestroyed() !== false) {
      return
    }
    const tileset = new Cesium3DTileset({
      url: `https://plateau.geospatial.jp/main/data/3d-tiles/${path}/tileset.json`
    })
    viewer.scene.primitives.add(tileset)
    setTileset(tileset)
    return () => {
      if (!viewer.isDestroyed()) {
        viewer.scene.primitives.remove(tileset)
      }
      setTileset(undefined)
    }
  }, [path, viewer])
 
  useEffect(() => {
    if (tileset != null) {
      tileset.style = new Cesium3DTileStyle({
        color: `color("${color}")`
      })
    }
  }, [color, tileset])
 
  return null
}

リスト6-9 src/PlateauTileset.tsx

■ Cameraコンポーネントの作成

次に、カメラの位置を制御するCameraコンポーネントを作成します。ここでは、東京駅を地表高度2kmから見下ろすように設定します。

import { Cartesian3, HeadingPitchRoll } from 'cesium'
import React, { useContext, useEffect } from 'react'
 
import { ViewerContext } from './Viewer'
 
export const Camera: React.FC = () => {
  const viewer = useContext(ViewerContext)
  useEffect(() => {
    if (viewer?.isDestroyed() !== false) {
      return
    }
    viewer.camera.setView({
      destination: Cartesian3.fromDegrees(139.745, 35.68, 2000),
      orientation: new HeadingPitchRoll(Math.PI / 2, -Math.PI / 4, 0)
    })
  }, [viewer])
 
  return null
}

リスト6-10 src/Camera.tsx

■ Appへの配置

作成したPlateauTilesetとCameraコンポーネントをAppに配置します。ここでは千代田区と中央区の建築物モデルを配置しています。

import React from 'react'
 
import { Camera } from './Camera'
import { PlateauTileset } from './PlateauTileset'
import { Viewer } from './Viewer'
 
export const App: React.FC = () => {
  return (
    <Viewer>
      <Camera />
      <PlateauTileset path='bldg/13100_tokyo/13101_chiyoda-ku/notexture' />
      <PlateauTileset path='bldg/13100_tokyo/13102_chuo-ku/notexture' />
    </Viewer>
  )
}

リスト6-11 src/App.tsx

ブラウザで開いて確認すると、図6-13のようになります。白の地表に建築物が表示されます。

図6-13 東京駅上空から見た3D都市モデルを表示したところ

6.3.4 _ 表現の調整

建築物が表示されたので、ライティングのパラメータを調整し、視覚的な表現を整えます。

■ 光の強度、球面調和関数、シャドウマップの設定

Lightingコンポーネントを作成し、ここでは光の強度と球面調和関数の調整、シャドウマップの有効化をします。

球面調和関数の調整はライティングから青みがかった色を消すことが目的で、sphericalHarmonicCoefficientsという球面調和関数の係数を定義しています。これは、本来はImage-based Lightingの高速な近似としてHDRIから生成する値ですが、適当な値でも目的を果たします。詳細はImageBasedLighting#sphericalHarmonicCoefficientsを参照してください。

import { Cartesian3 } from 'cesium'
import React, { useContext, useEffect } from 'react'
 
import { ViewerContext } from './Viewer'
 
// 適当な球面調和関数の係数
// https://cesium.com/learn/cesiumjs/ref-doc/ImageBasedLighting.html#sphericalHarmonicCoefficients
const sphericalHarmonicCoefficients = [
  new Cartesian3(1.5, 1.5, 1.5), // L_0,0
  new Cartesian3(1.25, 1.25, 1.25), // L_1,-1
  new Cartesian3(1.5, 1.5, 1.5), // L_1,0
  new Cartesian3(-1.25, -1.25, -1.25), // L_1,1
  new Cartesian3(-1, -1, -1), // L_2,-2
  new Cartesian3(1.25, 1.25, 1.25), // L_2,-1
  new Cartesian3(0, 0, 0), // L_2,0
  new Cartesian3(-1, -1, -1), // L_2,1
  new Cartesian3(-0, -0, -0) // L_2,2
]
 
export const Lighting: React.FC = () => {
  const viewer = useContext(ViewerContext)
  useEffect(() => {
    if (viewer?.isDestroyed() !== false) {
      return
    }
    const scene = viewer.scene
    scene.light.intensity = 5
    scene.sphericalHarmonicCoefficients = sphericalHarmonicCoefficients
 
    // シャドウマップの有効化とパラメータ調整。
    // 4096pxのシャドウマップはGPUによっては負荷が大きすぎるかもしれません。その場合は
    // `size`を2048にしたり、`softShadows`をfalseにしてください。
    scene.shadowMap.enabled = true
    scene.shadowMap.size = 4096
    scene.shadowMap.softShadows = true
    scene.shadowMap.darkness = 0.5
 
    // シャドウマップのZファイティングを避けるために非公開APIのnormalOffsetScaleを調
    // 整します。ブラウザやGPUドライバによっては不要かもしれません。
    ;(scene.shadowMap as any)._primitiveBias.normalOffsetScale = 5
  }, [viewer])
 
  return null
}

リスト6-12 src/Lighting.tsx

太陽高度の固定

次に、太陽の高度を固定するために、時刻を指定するためのClockコンポーネントを作成します。ここではブラウザから参照されるタイムゾーンがGMT+09:00(日本標準時)であることを想定し、2022年1月1日の13時を指定しています。

import { JulianDate } from 'cesium'
import React, { useContext, useEffect } from 'react'
 
import { ViewerContext } from './Viewer'
 
export const Clock: React.FC = () => {
  const viewer = useContext(ViewerContext)
  useEffect(() => {
    if (viewer?.isDestroyed() !== false) {
      return
    }
    JulianDate.fromDate(new Date(2022, 0, 1, 13), viewer.clock.currentTime)
  }, [viewer])
 
  return null
}

リスト6-13 src/Clock.tsx

Appへの配置

これらの作成したLightingとClockコンポーネントをAppに配置します。

import React from 'react'
 
import { Camera } from './Camera'
import { Clock } from './Clock'
import { Lighting } from './Lighting'
import { PlateauTileset } from './PlateauTileset'
import { Viewer } from './Viewer'
 
export const App: React.FC = () => {
  return (
    <Viewer>
      <Camera />
      <Clock />
      <Lighting />
      <PlateauTileset path='bldg/13100_tokyo/13101_chiyoda-ku/notexture' />
      <PlateauTileset path='bldg/13100_tokyo/13102_chuo-ku/notexture' />
    </Viewer>
  )
}

リスト6-14 src/App.tsx

ブラウザ上で表示を確認すると、次のようになります(図6-14)。視覚的な表現が整いました。

図6-14 ライティングを調整したところ

6.3.5 _ 地形の調整

Cesiumで表示される地表面はTerrain(地形)とImagery(画像)の2つから構成され、それぞれTerrainProviderImageryProvider が提供します。

Cesiumはデフォルトでは、WGS84地球楕円体を地形として描画します。一方、PLATEAUの3D都市モデルには標高が加味されています。日本の標高は東京湾の平均海面が基準です。日本水準原点(北緯35度40分37.9899秒、東経139度44分52.2492秒)のジオイド高は36.7071m です。したがって、デフォルトではほぼすべての建物が地表面から36.7071m以上浮いて表示されることになります(標高による)。

図6-15 デフォルトでは36.7071m以上浮いている

これを改善するため、「PLATEAU-Terrain配信サービス」の配信データを用いましょう。このサービスを用いたPlateauTerrainコンポーネントを作成します。

import { CesiumTerrainProvider, IonResource } from 'cesium'
import React, { useContext, useEffect } from 'react'

import { ViewerContext } from './Viewer'

export const PlateauTerrain: React.FC = () => {
  const viewer = useContext(ViewerContext)
  useEffect(() => {
    if (viewer?.isDestroyed() !== false) {
      return
    }
    // https://github.com/Project-PLATEAU/plateau-streaming-tutorial/blob/main/terrain/plateau-terrain-streaming.md
    viewer.terrainProvider = new CesiumTerrainProvider({
      url: IonResource.fromAssetId(770371, {
        accessToken:
          'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI5N2UyMjcwOS00MDY1LTQxYjEtYjZjMy00YTU0ZTg5MmViYWQiLCJpZCI6ODAzMDYsImlhdCI6MTY0Mjc0ODI2MX0.dkwAL1CcljUV7NA7fDbhXXnmyZQU_c-G5zRx8PtEcxE'
      })
    })
  }, [viewer])

  return null
}

リスト6-15 src/PlateauTerrain.tsx

作成したPlateauTerrainコンポーネントをAppに配置します。

import React from 'react'

import { Camera } from './Camera'
import { Clock } from './Clock'
import { Lighting } from './Lighting'
import { PlateauTerrain } from './PlateauTerrain'
import { PlateauTileset } from './PlateauTileset'
import { Viewer } from './Viewer'

export const App: React.FC = () => {
  return (
    <Viewer>
      <Camera />
      <Clock />
      <Lighting />
      <PlateauTerrain />
      <PlateauTileset path='bldg/13100_tokyo/13101_chiyoda-ku/notexture' />
      <PlateauTileset path='bldg/13100_tokyo/13102_chuo-ku/notexture' />
    </Viewer>
  )
}

リスト6-16 src/App.tsx

ブラウザ上での表示は、次のようになります。

図6-16 建物が浮かなくなった

6.3.6 _ 地図の表示

次に地表面に地図を表示しましょう。Cesiumには、いくつかのサービスに対応したImageryProviderが用意されています。

ここでは、Mapbox を用いて地図を表示します(一定以上の利用は有料ですので、サービスの仕様を理解し、登録をしたうえでアクセストークンをご用意ください)。

このチュートリアルのためのMapbox地図のスタイルを次に用意しました。

Username: shotamatsuda
Style ID: cl9302ng4000915rmrwd0m710

図6-17 地図を表示する

まず、MapboxStyleImageryProviderを扱うMapboxImageryコンポーネントを作成します。入力はaccessToken、usernameとstyleIdの3つです。

import { MapboxStyleImageryProvider } from 'cesium'
import React, { useContext, useEffect } from 'react'

import { ViewerContext } from './Viewer'

export interface MapboxImageryProps
  extends Pick<
    MapboxStyleImageryProvider.ConstructorOptions,
    'accessToken' | 'username' | 'styleId'
  > {}

export const MapboxImagery: React.FC<MapboxImageryProps> = ({
  accessToken,
  username,
  styleId
}) => {
  const viewer = useContext(ViewerContext)
  useEffect(() => {
    if (viewer?.isDestroyed() !== false) {
      return
    }
    const imageryLayer = viewer.imageryLayers.addImageryProvider(
      new MapboxStyleImageryProvider({
        accessToken,
        username,
        styleId,
        scaleFactor: true
      })
    )
    return () => {
      if (!viewer.isDestroyed()) {
        viewer.imageryLayers.remove(imageryLayer)
      }
    }
  }, [accessToken, username, styleId, viewer])

  return null
}

リスト6-17 src/MapboxImagery.tsx

作成したMapboxImageryをAppに配置します。accessTokenはMapboxに登録して得られるアクセストークンを使用してください。

import React from 'react'

import { Camera } from './Camera'
import { Clock } from './Clock'
import { EllipsoidTerrain } from './EllipsoidTerrain'
import { Lighting } from './Lighting'
import { MapboxImagery } from './MapboxImagery'
import { PlateauTileset } from './PlateauTileset'
import { Viewer } from './Viewer'

export const App: React.FC = () => {
  return (
    <Viewer>
      <Camera />
      <Clock />
      <Lighting />
      <EllipsoidTerrain />
      <MapboxImagery
        accessToken='取得したアクセストークン'
        username='shotamatsuda'
        styleId='cl9302ng4000915rmrwd0m710'
      />
      <PlateauTileset path='bldg/13100_tokyo/13101_chiyoda-ku/notexture' />
      <PlateauTileset path='bldg/13100_tokyo/13102_chuo-ku/notexture' />
    </Viewer>
  )
}

リスト6-18 src/App.tsx

ブラウザ上で表示を確認すると、次のようになります。

図6-18 地表面を表示したところ

6.4 _ 事例

Cesiumを使った例としては、以下のようなものがあります。

ARを活用した災害リスク可視化ツール

【企業名】株式会社福山コンサルタント東京支社
【分野】防災・防犯
【対象地域】東京都板橋区
【PLATEAU利用データ】建築物(LOD1、LOD2)、災害リスク(LOD1)
【他のデータとの掛け合わせ】対象河川の洪水浸水想定図作成業務報告書(荒川下流)、
浸水想定区域図データ(時系列データ、流速を含む電子データ)
【利用ソフトウェア】Cesium.js、Re:Earth、PostGIS
【活用概要】時系列の浸水深及び避難を開始するタイミングに応じた避難ルートを3D都市モデル上で表現し、水害範囲の拡大により避難行動が限定される様子を三次元的に可視化。
さらに、これをARアプリケーションで可視化し、住民の防災訓練等で活用することで、住民の水害に対する意識の啓発や避難行動の変容を促進。
【URL】https://www.mlit.go.jp/plateau/use-case/uc22-026/

住民個人の避難行動立案支援ツール

【企業名】株式会社福山コンサルタント東京支社
【分野】防災・防犯
【対象地域】埼玉県蓮田市
【PLATEAU利用データ】建築物(LOD1、LOD2)、災害リスク(LOD1)
【他のデータとの掛け合わせ】対象河川(元荒川)の洪水浸水想定区域図作成業務報告書、
国土地理院5mDEMデータ、避難ルートネットワークデータ
【利用ソフトウェア】Cesium.js、Re:Earth、PostGIS
【活用概要】3D都市モデルをベースとして、洪水による浸水が時系列に従って徐々に広がっていく様子を3次元化する。
さらに、浸水範囲に応じた適切な避難ルートを検索・可視化するシステムを開発することで、個々の建物から避難場所への避難ルートが浸水の広がりによって徐々に遮断され、最終的には孤立してしまうリスクをわかりやすく表現。
【URL】https://www.mlit.go.jp/plateau/use-case/uc22-041/

河川整備効果の見える化

【企業名】株式会社福山コンサルタント東京支社
【分野】防災・防犯
【対象地域】千葉県茂原市
【PLATEAU利用データ】建築物(LOD1、LOD2)、災害リスク(LOD1)
【他のデータとの掛け合わせ】対象河川の洪水浸水想定図作成業務報告書、一宮川整備計画報告書(改修スケジュールと対応する河川断面)、
一宮川浸水解析データ(氾濫原および河道)、H27国勢調査、資産分布
【利用ソフトウェア】Cesium.js、Re:Earth、PostGIS
【活用概要】3D都市モデルを活用し、一宮川沿川地域の河川整備事業の各段階において、水害リスクがどのように低減していくのかの分析を行い、
その結果を3次元的に表現する河川整備効果の可視化ツールを開発。
【URL】https://www.mlit.go.jp/plateau/use-case/uc22-034/

高度な浸水シミュレーション

【企業名】エム・アール・アイ リサーチアソシエイツ株式会社
【分野】防災・防犯
【対象地域】愛知県岡崎市
【PLATEAU利用データ】建築物(LOD1)、土地利用(LOD1)、地形(LOD1)
【他のデータとの掛け合わせ】対象地域の最大想定規模降雨データ、
河川データ(水位、洪水流量、排水)、破堤条件
【利用ソフトウェア】Cesium.js、独自ソフト
【活用概要】3D都市モデルを活用し、実際の水の広がりを精緻に演算する。
従来の手法よりもさらに精緻なシミュレーションを行うことで、高度な防災施策の立案を目指す。
【URL】https://www.mlit.go.jp/plateau/use-case/uc22-009/

地域防災支援プラグイン

【企業名】エム・アール・アイ リサーチアソシエイツ株式会社、株式会社Eukarya 
【分野】防災・防犯
【対象地域】鳥取県鳥取市
【PLATEAU利用データ】建築物(LOD1)、災害リスク(LOD1)
【他のデータとの掛け合わせ】施設系情報(CSV):防災関連施設・多くの人が集まる施設、非施設系情報(CSV):災害時役に立つ地物・災害時に障害になる地物、画像データ(PNG):ランドマークフラグ・外観写真
【利用ソフトウェア】Cesium.js、Re:Earth
【活用概要】3D都市モデルを活用して地域の避難施設の想定収容人数等の防災上必要な各種施設の詳細情報をインフォボックスとしてわかりやすく可視化するツールを開発し、住民によるワークショップでの活用を行う。
これにより、住民の防災情報へのアクセシビリティを向上させ、住民主体の防災まちづくりを推進することを目指す。
【URL】https://www.mlit.go.jp/plateau/use-case/uc22-018/

徒歩および車による時系列水害避難行動シミュレーション

【企業名】株式会社ライテック
【分野】防災・防犯
【対象地域】熊本県熊本市(南区の特定4校区および北側に隣接する沿岸地域の8校区)
【PLATEAU利用データ】建築物(LOD1)、地形(LOD1)
【他のデータとの掛け合わせ】避難者人口分布データ、道路ネットワークデータ、メッシュ別時系列浸水深データ
【利用ソフトウェア】Cesium.js、独自ソフト
【活用概要】3D都市モデルをベースとして、災害発生時の時系列的な徒歩および車による住民の避難行動をシミュレーションするシステムを開発する。
さらに、その結果に基づき、さまざまなパターンの水害や避難行動ごとの問題点の把握や、これに基づく校区レベルの地区防災計画の立案、住民一人ひとりの防災行動計画(マイタイムライン)の普及促進を目指す。
【URL】https://www.mlit.go.jp/plateau/use-case/uc22-039/

都市OSと連携した都市政策シミュレーション

【企業名】日本電気株式会社、パシフィックコンサルタンツ株式会社、株式会社Eukarya
【分野】まちづくり
【対象地域】香川県高松市
【PLATEAU利用データ】建築物(LOD1)、都市計画決定情報(LOD1)、災害リスク(LOD1)
【他のデータとの掛け合わせ】パーソントリップ調査データ、交通ネットワークデータ、
交通系ICカードデータ
【利用ソフトウェア】Cesium.js、Re:Earth、FIWARE
【活用概要】FIWAREをデータ統合のための都市OSとし、これにオープンソースのWebGISであるRe:Earthと接続することで、地理空間データの管理・分析・可視化プラットフォームを構築する。
これをデータ連携基盤として、3D都市モデルのもつ建物や土地利用のデータ、人々の移動データ等を組み合わせて都市構造の将来変化を予測する都市政策シミュレーション技術を開発。
【URL】https://www.mlit.go.jp/plateau/use-case/uc22-042/

まちなかウォーキングのための健康アプリ

【企業名】エヌ・ティ・ティ・コミュニケーションズ株式会社、アジア航測株式会社
【分野】まちづくり
【対象地域】岐阜県岐阜市
【PLATEAU利用データ】建築物(LOD1、LOD2)、道路(LOD1、LOD2、LOD3)
【他のデータとの掛け合わせ】道路詳細データ(道路LOD3.0データ・歩行空間ネットワークデータ)、
推奨ルートレコメンド用データ(勾配データ・舗装データ・歩車区分)、
バイタルデータ(CSV):年齢・脈拍数・位置情報
【利用ソフトウェア】Cesium.js、PostGIS
【活用概要】日常的なまちなかの移動時に運動効果の高いウォーキングができるよう、3D都市モデルの詳細な道路モデルやユーザーの属性情報を活用したウォーキングコースのレコメンドおよびウォーキング後のフィードバックを行うアプリを開発する。
開発したアプリを用いてフィールドでの実証を行い、アプリの有用性や市民の健康意識への啓発効果を検証する。
【URL】https://www.mlit.go.jp/plateau/use-case/uc22-022/

3D都市モデルプラグイン共有プラットフォーム

【企業名】株式会社Eukarya
【分野】まちづくり
【対象地域】大阪府摂津市
【PLATEAU利用データ】建築物(LOD1、LOD2)
【他のデータとの掛け合わせ】摂津市が保有するオープンデータ
【利用ソフトウェア】Cesium.js、Re:Earth
【活用概要】オープンソースのWebGISソフトウェアである「Re:Earth」を活用し、3D都市モデルのユースケース開発に利用可能なRe:Earthのシステムをプラグインとして誰でも開発して公開・利用ができる「3D都市モデルプラグイン共有プラットフォーム」を開発する。
このプラットフォームでは、エンジニアが地域の課題に合わせたさまざまなユースケース用プラグインを開発し、アップロードして共有することができる。
【URL】https://www.mlit.go.jp/plateau/use-case/uc22-029/

ドローン最適ルートシミュレーション

【企業名】株式会社トラジェクトリー
【分野】新サービス開発
【対象地域】愛知県豊川市
【PLATEAU利用データ】建築物(LOD1、LOD2)、
道路(LOD1)、都市設備(LOD1)、土地利用(LOD1)
【他のデータとの掛け合わせ】なし
(ソフトウェアを利用して、PLATEAUのCityGMLから風・電波のシミュレーションデータ等を生成)
【利用ソフトウェア】CesiumJS、独自ソフト
【活用概要】3D都市モデルを活用してグランドリスク、風況、電波伝搬状況等の複合的なリスク要素を評価値として空間上にマッピングし、
安全性の高いルートを生成するシミュレータを開発することで、誰もがドローンを安全に飛行させることができる社会の実現を目指す。
【URL】https://www.mlit.go.jp/plateau/new-service/4-013/

【文】

松田聖大(Takram)*6.3を執筆
大澤文孝

【監修】

高田知典(Symmetry Dimensions Inc.)
松田聖大(Takram)