プログマのプログラマ日記

技術メモや自社サービスに関する記事を書いていく予定です。

Cesiumの標高計算調査その1:sampleTerrainMostDetailedの処理の流れ

Cesiumで標高計算処理を他言語に移植するために関連コードを調べた時のメモ書きです。 本記事では処理の入り口となるsampleTerrainMostDetailedの処理の大まかな流れについて調べています。

地形データのフォーマット

今回調査したかったのは以下の2つのデータを使った標高計算です。

主要なクラス・関数

標高計算処理で使われる主要なクラス・関数は以下の通りです。

  • sampleTerrainMostDetailed

    Cesiumで標高を取得する際の入り口となるグローバル関数。

    指定した点における、使用可能な最大の地形レベル(地形の詳細度)を調べてからsampleTerrainを呼び出す。

  • sampleTerrain

    地形レベル指定で、指定した点の標高を取得するグローバル関数。

  • TerrainProvider

    地形データやメタ情報(タイルのスキーマや利用可否情報)を取得するためのインタフェース。

    sampleTerrain, sampleTerrainMostDetailedから利用される。

    などの実装があるが、heightmap, quantized-meshを使う場合はCesiumTerrainProviderを使うことになる。

  • TileAvailability

    TerrainProviderが配信するタイルの特定のタイルの利用可否や、ある地点で利用可能な最大地形レベルなどを取得するためのクラス。

    TerrainProviderのavailabilityプロパティとして取得できる。

  • TilingScheme

    地形タイルのスキーマ(タイルのXY番号に対応する矩形範囲など)を定義するインタフェース。

    TerrainProviderのtilingSchemeプロパティとして取得できる。

    の実装があるが、CesiumTerrainProviderを使う場合はGeographicTilingSchemeのスキーマが使われることになる(*1)

  • TerrainData

    地形タイル1枚分の地形データを表現するインタフェース。

    TerrainProviderのrequestTerrainGeometryの戻り値。

    CesiumTerrainProviderを使う場合は使用するデータに応じて

    の実装が使われる。

*1) CesiumTerrainProviderのtilingSchemaについているコメントではGeographicTilingSchemeが返ることになっている一方で、 tilingSchemaを初期化するコードの実装を確認するとlayers.jsonのprojectionに"EPSG:3857"が設定されている場合はWebMercatorTilingSchemeが設定されるケースもあるみたいです。

cesim-terrain-builderでheightmap・quantized-meshデータを作るとlayers.jsonのEPSGには4326が入るので、原則GeographicTilingSchemeが返ってくると考えていれば良いのかもです。

標高計算処理のメイン処理

詳細は後述しますが、sampleTerrainMostDetailedを呼び出してから最終的に標高値を計算している関数はTerrainDataのinterpolateHeightであることがわかりました。

heightmap使用時の標高計算はHeightmapTerrainDataのinterpolateHeight

quantized-mesh使用時の標高計算はQuantizedMeshTerrainDataのinterpolateHeight

の内容を参考にすれば良さそうです。

(以下はsampleTerrainMostDetailedが呼ばれてからTerrainData#interpolateHeightが呼ばれるまでの処理の流れです。)

sampleTerrainMostDetailedの処理概要

この関数は、与えられた点列の利用可能な地形タイルの最大レベルをterrainProviderから取得し、sampleTerrainを呼び出す処理を行っています。

処理の流れは以下の通り。

sequenceDiagram;
participant app as アプリケーション<br/>コード
participant sampleTerrainMostDetailed as sampleTerrain<br/>MostDetailed
participant sampleTerrain as sampleTerrain
participant terrainProvider as terrain<br/>Provider
app-)+sampleTerrainMostDetailed: 1. sampleTerrain<br/>MostDetailed呼び出し
loop 点列でループ
sampleTerrainMostDetailed-)+terrainProvider: 2. その地点で利用可能な地形タイルの最大レベルを取得
terrainProvider--)-sampleTerrainMostDetailed: 最大レベル
end
sampleTerrainMostDetailed-->sampleTerrainMostDetailed: 3. 最大レベルで<br/>点列をグルーピング
sampleTerrainMostDetailed-)+sampleTerrain: 4. グループ単位で<br/>sampleTerrain<br/>呼び出し
sampleTerrain--)-sampleTerrainMostDetailed: 標高値入りの点列
sampleTerrainMostDetailed-)sampleTerrainMostDetailed: 5. 最大レベルが<br/>更新された点の<br/>再サンプリング
sampleTerrainMostDetailed--)-app: 標高値入りの点列
  1. アプリケーションコードがsampleTerrainMostDetailedを呼び出す。

    引数としてterrainProviderと標高を取得したい点列を渡す。

  2. 点列の各点ごとに利用可能な地形タイルの最大レベルを取得する。

    最大レベルはterrainProviderから取得する。

    厳密にはterrainProviderから取得したtileAvailabilityやtileSchemaを使ってごにょごにょしているのだが、シーケンス図からは省略している。

  3. 2の最大レベルで点列をグルーピングする

  4. 3のグループ単位でsampleTerrainを呼び出す。

  5. 4の呼出し後に利用可能な地形タイルの最大レベルが更新された点があった場合、該当する点を再サンプリングする。詳細な流れはこちらを参照。

sampleTerrainの処理概要

地形レベル指定で、与えられた点列の標高を取得します。

処理の流れは以下の通り。

sequenceDiagram;
participant sampleTerrainMostDetailed as sampleTerrain<br/>MostDetailed
participant sampleTerrain as sampleTerrain
participant terrainProvider as terrain<br/>Provider

sampleTerrainMostDetailed-)+sampleTerrain: 呼び出し
sampleTerrain->sampleTerrain:1.引数の検証
sampleTerrain->>+terrainProvider: 2. 点列のタイル情報取得
terrainProvider-->>-sampleTerrain: 
sampleTerrain->>sampleTerrain: 3. tileRequest<br/>配列の生成 
loop 4. tileRequest配列でループ
 sampleTerrain-)+terrainProvider: 5. タイルデータ要求
 create participant tileData as terrainData
 terrainProvider->>tileData: 
 terrainProvider-)-sampleTerrain: 
 create participant InterpolateFunction as interpolateFunction
 sampleTerrain->>InterpolateFunction: 6. tileRequest内の点列の標高を取得するための関数を生成
 sampleTerrain->>+InterpolateFunction: 7. 呼び出し
 loop tileRequest中の点列でループ
  InterpolateFunction->>tileData: 8. 点の高さデータを取得
  tileData-->>InterpolateFunction: 結果
 end
 InterpolateFunction-->>-sampleTerrain: 結果
end
sampleTerrain--)-sampleTerrainMostDetailed: 処理結果(標高入り点列)

1.引数を検証したのち、doSampling関数を呼び出す。

2.点列のタイル情報(各点がどの地形タイルに含まれているか)を取得する。(doSampling内の処理)

3.2の情報に従って点列をタイル単位でグループ化してtileRequestの配列を生成する。(doSampling内の処理) tileRequestの内容:

   {
       x: タイル番号X,
       y: タイル番号Y,
       level: 地形レベル,
       tilingScheme: tilingScheme,
       terrainProvider: terrainProvider,
       positions: タイル内の点列,
   };

4.3のtileRequest単位で標高計算の処理を行う

全てのtileRequestに対してattemptConsumeNextQueueItemを実行する。具体的な流れは以下の通り。

5.terrainProviderからrequestTileGeometryでTerrainDataを取得する。

6.tileRequest内の点列の標高を取得するための関数を生成する。

createInterporlateFunctionという関数で生成。

createInterporlateFunctionは、attemptConsumeNextQueueItem関数内のここで呼び出している。

7.6で生成した関数を呼び出す。引数は5で取得したタイルデータ。

関数の中身は

cesium/packages/engine/Source/Core/sampleTerrain.js at 1.115 · CesiumGS/cesium (github.com)

   return function (terrainData) { // このブロック
       let isMeshRequired = false;
       for (let i = 0; i < tilePositions.length; ++i) {
         const position = tilePositions[i];
           ...

8.tileRequest内の全ての点について点の高さを取得する。具体的な流れは以下の通り。