Cesiumで標高計算処理を他言語に移植するために関連コードを調べた時のメモ書きです。 本記事では処理の入り口となるsampleTerrainMostDetailedの処理の大まかな流れについて調べています。
地形データのフォーマット
今回調査したかったのは以下の2つのデータを使った標高計算です。
quantized-mesh-1.0:
heightmap-1.0
主要なクラス・関数
標高計算処理で使われる主要なクラス・関数は以下の通りです。
-
Cesiumで標高を取得する際の入り口となるグローバル関数。
指定した点における、使用可能な最大の地形レベル(地形の詳細度)を調べてからsampleTerrainを呼び出す。
-
地形レベル指定で、指定した点の標高を取得するグローバル関数。
-
地形データやメタ情報(タイルのスキーマや利用可否情報)を取得するためのインタフェース。
sampleTerrain, sampleTerrainMostDetailedから利用される。
- EllipsoidTerrainProvider
- CesiumTerrainProvider
- VRTheWorldTerrainProvider
- GoogleEarthEnterpriseTerrainProvider
などの実装があるが、heightmap, quantized-meshを使う場合はCesiumTerrainProviderを使うことになる。
-
TerrainProviderが配信するタイルの特定のタイルの利用可否や、ある地点で利用可能な最大地形レベルなどを取得するためのクラス。
TerrainProviderのavailabilityプロパティとして取得できる。
-
地形タイルのスキーマ(タイルのXY番号に対応する矩形範囲など)を定義するインタフェース。
TerrainProviderのtilingSchemeプロパティとして取得できる。
の実装があるが、CesiumTerrainProviderを使う場合はGeographicTilingSchemeのスキーマが使われることになる(*1)
-
地形タイル1枚分の地形データを表現するインタフェース。
TerrainProviderのrequestTerrainGeometryの戻り値。
CesiumTerrainProviderを使う場合は使用するデータに応じて
- HeightmapTerrainData(heightmap)
- QuantizedMeshTerrainData(quantized-mesh)
の実装が使われる。
*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: 標高値入りの点列
アプリケーションコードがsampleTerrainMostDetailedを呼び出す。
引数としてterrainProviderと標高を取得したい点列を渡す。
点列の各点ごとに利用可能な地形タイルの最大レベルを取得する。
最大レベルはterrainProviderから取得する。
厳密にはterrainProviderから取得したtileAvailabilityやtileSchemaを使ってごにょごにょしているのだが、シーケンス図からは省略している。
2の最大レベルで点列をグルーピングする
3のグループ単位でsampleTerrainを呼び出す。
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を実行する。具体的な流れは以下の通り。
- doSampling内でdrainTileRequestQueue を呼び出す。
- drainTileRequestQueueではattemptConsumeNextQueueItemを呼び出してtileRequestを一つ処理する。
- tileRequest配列が空になるまで再帰的にdrainTileRequestQueueが呼ばれる
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内の全ての点について点の高さを取得する。具体的な流れは以下の通り。
- ループ内でinterpolateAndAssignHeightを呼ぶ
- interpolateAndAssignHeight内でterrainDataのinterpolateHeightを呼びだして点の高さを取得する