すべてのプロダクト
Search
ドキュメントセンター

PolarDB:GanosBase に基づく GeoSOT 地理グリッドモデル: UAV 経路計画

最終更新日:Apr 28, 2025

新エネルギー技術は、低高度経済の成長を促進し、戦略的産業として推進してきました。しかし、三次元性、地域性、統合性という特徴を持つ低高度活動は、安全で効率的な運用を確保するために対処しなければならない独自の技術的課題を提示します。GanosBase GeoSOT 地理グリッドエンジンは、グリッドベースの経路計画機能を提供し、数値標高モデル(DEM)、数値表面モデル(DSM)、斜め写真などのデータ構造を使用して、複雑な環境での UAV 経路計画アプリケーションの開発を促進します。

GanosBase 地理グリッドモデル

地理グリッドは、地球の表面を表す多角形グリッドの集合です。地理空間における地物位置情報の表現や、他の種類の時空間データの統合に使用できます。この計算方法では、通常、階層分割を用いて地球の表面を処理します。指定されたサイズの多角形グリッドで表面を近似することにより、地理空間測位と地物記述を組み合わせ、誤差を許容範囲内に収めます。各グリッドはエンコードされ、一意のマッピングを提供します。3D 地理グリッドは、緯度と経度のディメンションに加えて、高さのディメンションもメッシュ化およびエンコードします。

GanosBase 地理グリッドエンジンは、現在、GeoSOT と H3 の 2 種類の地理グリッドをサポートしています。

  • GeoSOT は、中国の地球分割理論に基づいて開発された、離散的なマルチスケール位置識別システムです。

  • H3 は、地球の表面を覆うために Uber によって開発された 2 次元地理グリッドです。六角形グリッドを利用しています。詳細については、「H3 地理グリッドの機能とベストプラクティス」をご参照ください。

GanosBase は、次の図に示すように、縮退グリッド計算をサポートしています。階層グリッド構造を活用することで、簡潔なグリッドの組み合わせを使用して空間範囲を効果的に表現します。さらに、GanosBase の独自開発の地理グリッドインデックスにより、効率的なグリッドエンコーディングクエリと高速集約が可能になります。

7

背景情報

新エネルギー技術は、低高度経済の成長を促進し、戦略的産業として推進してきました。これは、有人および無人の航空機の低高度飛行活動を活用して、さまざまなセクターにわたる統合開発を促進する包括的な経済エコシステムです。そのアプリケーションは、従来のゼネラルアビエーションとドローンがサポートする低高度生産サービスを網羅しています。産業界、農業、サービス業で広く使用されています。低高度経済は、現代の産業システムの形成に役立ち、非常に明るい未来を提供します。

従来の地上活動とは異なり、低高度活動は、三次元性、地域性、統合性という特徴があります。これらの特性により、低高度運用の安全かつ秩序ある実施を確保するために早急に解決する必要があるさまざまな技術的課題が生じます。

  • 三次元性: これは、活動空間の三次元性に現れています。真高度で通常 1000 メートル未満の低高度空域は、異なる地域の特性とニーズに応じて指定された特定の高度範囲によって定義されます。これにより、平面経済から三次元経済への移行が可能になります。

  • 地域性: これは、適用範囲の地域性によって反映されます。小型航空機、小型ルート、零細企業を特徴とする低高度経済は、地域性と局所性を示しています。これは、航空や高速鉄道の大規模で統合された経済とは対照的です。

  • 統合性: これは、運用モードの統合に関係しています。輸送中心の経済とは異なり、低高度経済は産業サービスを重視しています。産業と航空の複合モデルを採用して、関連産業に航空ソリューションとサポートを提供し、効率性と費用対効果を高めています。

低高度活動の分析により、低高度地域には道路網がないことが明らかになりました。低高度経済と地域安全の成功裡の実施にとって重要な要素には、低高度デバイスの安全な移動と到着の確保、低高度地域におけるアクティブサイクルの合理的な分割、航空機活動計画などがあります。

特徴

GanosBase 地理グリッドエンジンは、グリッドベースの経路計画機能を提供します。数値標高モデル(DEM)、数値表面モデル(DSM)、斜め写真などのデータ構造を使用して、複雑な環境での UAV 経路計画シナリオで使用できます。次の関数を使用できます。

  • ST_SetCost

    この関数は、建物などの物理的なエンティティや、レーダースキャンエリアや風況場などの空間範囲である障害物の移動コストを設定します。ST_SetCost を使用する前に、障害物の空間範囲をグリッド配列に変換する必要があります。その後、それを使用して移動コストを設定できます。

  • ST_3DGridPath

    UAV 経路計画のコア関数である ST_3DGridPath は、開始点と終了点、および通過コストデータに基づいて最適な 3 次元グリッド経路を計算します。さまざまな経路アルゴリズム、移動方法、距離計算方法をサポートしています。

  • ST_MatchGridLevel

    この関数は、地形標高データの解像度に基づいて、使用可能な最高のグリッドレベルを決定します。経路計算では、使用可能な最高のグリッドレベルよりも高いグリッドレベルを使用しないでください。

  • ST_CostUnion

    この 関数 は、すべての障害物グリッドコスト配列をマージします。縮退グリッドのマージがサポートされています。重複するグリッドには最大コストが適用され、最終的な計画ルートの入力として予約されます。

技術的メリット

GanosBase GeoSOT 地理グリッドエンジンは、UAV 経路計画にいくつかの技術的メリットを提供します。

  • ラスターやサーフェスグリッドなど、すべての入力データに対して GanosBase 内で統一された標準データモデル。

  • GanosBase の内部関数による完全なデータ処理、および経路計画ヘルパー関数の包括的なセット。

  • 縮退グリッド技術を使用して、ストレージのオーバーヘッドを削減し、計算速度を向上させます。

  • 経路計画アルゴリズム、移動方法、距離計算方法のさまざまなパラメータオプションにより、多様なニーズに対応します。

ベストプラクティス

次のセクションでは、特定の公園の斜め写真データを使用して、現実世界のシナリオにおける 3D 経路計画の適用例を紹介します。

推奨仕様

最適なパフォーマンスを得るには、次の PolarDB クラスタ仕様をお勧めします。

  • データベースエンジン: PolarDB for PostgreSQL 14

  • エンジンバージョン: 14.12.23.1

  • CPU: 4 コア以上

  • メモリ: 16 GB 以上

  • ディスク容量: 100 GB 以上

  • GanosBase バージョン: 6.7 以上

説明

OSGB データのインポートと計算中は、大量のメモリが必要になります。16 GB を超えるメモリを使用することをお勧めします。

テストデータ

7

データのインポートと処理

  1. グリッド経路計画に必要な拡張機能をインストールします。

    説明

    次のステートメントを実行して拡張機能をインストールするときに例外が発生した場合は、お問い合わせください。

    CREATE EXTENSION IF NOT EXISTS ganos_geomgrid CASCADE;
    CREATE EXTENSION IF NOT EXISTS ganos_utility CASCADE;
  2. ST_ImportOSGB 関数を使用して、OSS に保存されているテストデータをインポートします。詳細については、「ST_ImportOSGB」をご参照ください。

    SELECT 
      ST_ImportOSGB(
        'test_oblique',
        'OSS://<access_id>:<secrect_key>@<Endpoint>/<bucket>/path_to/file', 
        '{"parallel":16,"project":"building"}');
  3. 特定レベルのタイルを GLB 形式に変換し、中間型として SfMesh 型を使用して一時テーブル temp_glb に保存します。

    SELECT 
      ST_ImportGLB(
        'temp_glb', 
        ST_AsGLB(ST_Combine(tile)), 
        'temp_glb_1', 
        '{"ignore_texture":true,"ignore_normal":true}'
      ) 
    FROM test_oblique_tile WHERE lod = 19 AND project_name = 'building';
    説明
    • ST_ImportGLB 関数を呼び出すときは、不要なテクスチャ(ignore_texture)と法線(ignore_normal)を無視します。

    • lod = 19 のタイルがサンプリングされます。ニーズに合ったタイルレベルを選択できます。タイルレベルが高いほどサンプリング時間は長くなりますが、精度は高くなります。

    • テストでは、歪みデータの一般的な命名規則が使用されます。たとえば、Tile_+006_+004_L14_0.osgb では、タイルレベルはファイル名から取得できます。

    • 歪みデータが不規則な命名規則を使用している場合、タイルレベルを取得できない場合があります。そのような場合、HERE lod = 19WHERE children IS NULL に置き換えて、最高の精度を持つリーフノードタイルをサンプルタイルとして選択できます。

  4. 一時データを更新し、EPSG: 4490 座標系に投影します。

    UPDATE temp_glb 
    SET 
      gltf_data = ST_Transform(
        ST_Flatten(
          ST_SetSrid(
            ST_Translate(
              ST_YupToZup(gltf_data), 
              x_off, y_off,z_off), 
            srid), 
          FALSE), 
        4490) 
    FROM 
      (SELECT 
          srid, 
          ST_X(anchor) x_off, 
          ST_Y(anchor) y_off, 
          ST_Z(anchor) z_off 
        FROM test_oblique WHERE project_name = 'building') metadata 
    WHERE gltf_id = 'temp_glb_1';

    上記のステートメントは、次の操作を実行します。

    • OSGB を GLB に変換する場合、Y 軸は垂直基準として機能します。座標を正しく変換するには、ST_YupToZup 関数を呼び出して、Z 軸上方向の構成に再配向する必要があります。

    • 歪みデータは通常、相対座標を使用するため、ST_Translate 関数を使用して、アンカーポイントを使用して各ポイントにオフセットを適用する必要があります。これにより、各ポイントの座標が絶対座標に変換されます。その後、ST_SetSrid 関数を使用して正しい SRID が確立されます。

      説明

      この操作では、元の歪みデータに完全なメタデータ記述ファイル(metadata.xml)が必要です。そうでない場合、アンカーポイントと座標系を正しく取得できません。

    • 回転行列を含む SfMesh オブジェクトの場合、座標投影のために回転行列を実際の座標に適用するには、事前フラット化(ST_Flatten)が必要です。

    • グリッドの座標系は CGC 2000 として定義されているため、ST_Transformを使用して、各ポイントを EPSG: 4490 座標系に再投影する必要があります。

  5. 中間データを地理グリッド(GeomGrid)に変換します。

    -- グリッドテーブルを作成します。
    CREATE TABLE IF NOT EXISTS building_grid
    (
       id   SERIAL,
       grid GEOMGRID,
       grid_type TEXT
    );
    -- GeomGrid データを生成し、グリッドテーブルに挿入します。
    INSERT INTO building_grid (grid, grid_type)
    SELECT grid, 'border'
    FROM
      (SELECT unnest(ST_As3DGrid(gltf_data, 24, TRUE)) grid
       FROM temp_glb WHERE gltf_id = 'temp_glb_1' ) tmp;
    -- 一時テーブルを削除します
    DROP TABLE temp_glb;

    ST_As3DGrid および 関数を使用すると、特定の要件に基づいて地理グリッドの精度レベルをカスタマイズできます。精度が高いほどグリッドは詳細になりますが、サンプリング時間も大幅に増加します。次の表に、さまざまなグリッドレベルのおおよそのサイズを示します。

    レベル

    おおよそのサイズ

    レベル

    おおよそのサイズ

    レベル

    おおよそのサイズ

    0

    グローバル

    11

    29.6 km

    22

    15.5 m

    1

    地球の 1/4

    12

    14.8 km

    23

    7.7 m

    2

    -

    13

    7.4 km

    24

    3.9 m

    3

    -

    14

    3.7 km

    25

    1.9 m

    4

    -

    15

    1.8 km

    26

    1.0 m

    5

    -

    16

    989.5 m

    27

    0.5 m

    6

    890.5 km

    17

    494.7 m

    28

    24.2 cm

    7

    445.3 km

    18

    247.4 m

    29

    12.0 cm

    8

    222.6 km

    19

    123.7 m

    30

    6.0 cm

    9

    111.3 km

    20

    61.8 m

    31

    3.0 cm

    10

    59.2 km

    21

    30.9 m

    32

    1.5 cm

グリッド表示形式

  • 生成されたグリッドは、おおよその形状を表示するために GLB 形式でエクスポートできます。

    SELECT 
      ST_AsGLB(
        ST_ZupToYup(
          ST_3DRemoveDuplicateVertex(
            ST_MergeGeomByMaterial(
              ST_Triangulate(
                ST_Translate(
                  ST_Transform(
                    ST_Flatten(
                      ST_Collect(
                        ST_AsMeshgeom(array (SELECT grid FROM building_grid WHERE grid_type = 'border')):: sfmesh[]),
                      TRUE
                    ), 
                    srid
                  ), 
                  - ST_X(anchor), - ST_Y(anchor), - ST_Z(anchor)
                )
              )
            ), 
            0.1
          )
        )
      ) 
    FROM test_oblique WHERE project_name = 'building';

    上記のステートメントは、次の操作を実行します。

    • ST_AsMeshGeom 関数を使用して、グリッド配列を MeshGeom 配列に変換します。

    • MeshGeom 配列型を SFMesh 配列に変換し、ST_Collect または を使用してそれらを単一の SfMesh オブジェクトに結合します。

    • ST_Flatten を使用して集約オブジェクトを簡略化し、ST_Transform を使用して元の座標系に投影します。

    • 集約オブジェクト全体にオフセットを適用し(ST_Translate)、すべての座標点を原点に対する相対位置として再配置します。

    • ST_Triangulate または 関数を使用して集約オブジェクトを三角形分割し、後続の処理ステップを合理化します。

    • ST_MergeGeomByMaterial 関数と ST_3DRemoveDuplicateVertex 関数を組み合わせて使用​​して、データ量を効果的に削減し、レンダリング負荷を軽減します。

    • ST_YupToZup 関数を呼び出して、GLTF のようにデータを左手座標系から右手座標系に変換します。次に、ST_AsGLB 関数を使用して、データを GLB 形式でエクスポートします。

    次の表は、さまざまなレベル(レベル 25 とレベル 24 を使用)の効果を比較しています。

    レベル

    結果

    生データ

    70414975c9d1496ea6e85e321f917e06

    レベル 25

    c11c33b7a03d464584d6ca5753b886e3

    レベル 24

    24bf8942858a469f8ca33ef071644701

  • 次のステートメントを使用して、グリッドオブジェクトから Cesium での読み取りに適したデータを生成します。

    -- 簡単に呼び出せるように関数を定義します。
    CREATE OR REPLACE FUNCTION GridToJson(geomgrid[])
    RETURNS json AS $$
    SELECT to_json(
            array_agg(
                json_build_object('center', array_to_json(array[ST_X(center), ST_Y(center), ST_Z(center)]), 
                               'size', array_to_json(array[ST_DistanceSpheroid(min_point, ST_Point(ST_X(max_point), ST_Y(min_point), 4490)), ST_DistanceSpheroid(min_point, ST_Point(ST_X(min_point), ST_Y(max_point), 4490)), z]))))
    FROM
      (SELECT ST_Transform(ST_PointZ(ST_X(st_centroid(geom)), ST_Y(st_centroid(geom)), (ST_ZMax(geom) + ST_ZMin(geom)) / 2, 4490), 4479) center,
              ST_SetSrid(BOX[0]::geometry, 4490) min_point,
              ST_SetSrid(BOX[1]::geometry, 4490) max_point,
              ST_ZMax(geom) - ST_ZMin(geom) z
       FROM
         (SELECT ST_ASBox(grid) BOX, ST_AsGeometry(grid) geom
          FROM (SELECT unnest($1) grid) a)b)c $$
    LANGUAGE 'sql';
    -- GridToJson 関数を呼び出して結果を生成します。
    SELECT GridToJson(array (SELECT grid
                         FROM building_grid WHERE grid_type = 'border')) ;
    説明

    上記のステートメントで作成された GridToJson 関数は、グリッドオブジェクトを、各グリッド要素の中心位置とサイズを含む JSON 配列に変換します。このプロセスでは、ST_Transform 関数が呼び出され、グリッドオブジェクトの空間参照系が SRID 4490 から SRID 4479 に変換されます。その結果、JSON 配列の center フィールドの座標値は SRID 4479 になります。この設定は必要に応じて調整できます。

    返される結果は JSON 配列です。

    [
      {
        "center": [-1583397.2647956165,5313841.088458569,3142388.7651142543],
        "size": [3.3600142470488144,3.848872238575258,3.3680849811062217]
      },
      ...
    ]
    • center: EPSG: 4479 座標系に投影された各グリッドの中心点。

    • size: 緯度/経度/高さ方向の各グリッドのおおよそのサイズ。

    Cesium では、上記のデータは次の形式でロードできます。

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="utf-8">
      <script src="https://cesium.com/downloads/cesiumjs/releases/1.89/Build/Cesium/Cesium.js"></script>
      <link href="https://cesium.com/downloads/cesiumjs/releases/1.89/Build/Cesium/Widgets/widgets.css" rel="stylesheet">
      <style>
        html,
        body,
        #cesium_container {
          width: 100%;
          height: 100%;
          margin: 0;
          padding: 0;
          overflow: hidden;
          }
      </style>
    </head>
    <body>
      <div id="cesium_container"></div>
      <script>
        Cesium.Ion.defaultAccessToken = YOUR_CESIUM_TOKEN
        const viewer = new Cesium.Viewer('cesium_container', ['timeline', 'animation', 'infoBox', 'navigationHelpButton'].reduce((opts, opt) => (opts[opt] = false) || opts, {}))
        
        // 表示するグリッドを指定します。
        const grids = [{ "center": [-1583315.44750805, 5313895.98214751, 3142303.525767127], "size":... 
        const add_grids = (grids, material) =>
          grids.forEach(({ center, size }) => viewer.entities.add({
              position: new Cesium.Cartesian3(...center),
              box: {dimensions: new Cesium.Cartesian3(...size),
                material, outline: true, outlineColor: Cesium.Color.BLACK}}))
          
        // 結果を半透明の赤で表示します。
    	add_grids(grids, Cesium.Color.RED.withAlpha(0.2))
          
        // グリッド位置にジャンプします
        viewer.zoomTo(viewer.entities)
      </script>
    </body>
    </html>

    レベル 24 グリッドを歪みデータに重ねます。歪みデータをオーバーレイするコードについては、「付録」をご参照ください。次の図は結果を示しています。

    7

グリッド経路計画

グリッドを構築した後、ST_3DGridPath 関数を使用して、2 つのポイントを開始点と終了点として、シナリオでグリッドに基づく 3 次元経路を計画できます。

-- 開始点、終了点、および検索可能な範囲を定義します。
WITH vals (start_p, end_p, box_range) AS (
  VALUES 
    (
      ST_PointZ(106.59182291666669, 29.70644097140958, 355.322855208642, 4490), 
      ST_PointZ(106.59244791666666, 29.707135415854022, 348.58669235736755, 4490), 
      'BOX3D(106.591788194444 29.7062326380763 341.85053662386713, 106.592899305556 29.7071701380762 362.0590251699541)' :: box3d
    )), 
border AS (
  SELECT DISTINCT grid, grid_type 
  FROM building_grid, vals 
  WHERE 
    -- 範囲内のグリッドのみを計算します。
    -- 計算に関係するすべてのジオメトリタイプには、EPSG: 4490 の空間参照が必要です。
    ST_3DIntersects(ST_SetSrid(box_range :: meshgeom, 4490), grid) 
    OR ST_3DContains(ST_SetSrid(box_range :: meshgeom, 4490), grid)
) 
SELECT ST_3DGridPath(start_p, end_p, box_range, array[ST_SetCost( array (SELECT grid FROM border WHERE grid_type = 'border' ), -1)], '{"algorithm":"astar","movement":"cross","distance":"euclidean"}') result 
FROM vals;

ST_SetCost 関数は、グリッドの移動コストを設定します。

  • 通行不可領域は -1 に固定されています。

  • 他の設定が必要ない場合、通行不可領域の移動コストは 1 に固定されます。

GridToJson 関数を使用して、Cesium で結果を表示します。

tiu6hs7lgzu44_9abd608ffe3b40a0b61e50c8eb68f3ad

tiu6hs7lgzu44_2bd2c03c8d3d414e9a4d465d96f33e83tiu6hs7lgzu44_d496f4033f4748e1877bbf968e656824

移動方法を strict_octothorpe に変更すると、結果は次の図のようになります。

tiu6hs7lgzu44_e4c154f88f4c4018969a2492b97f49f2

tiu6hs7lgzu44_64f6ef2d064d4e11ba56915ccbf0f1d7tiu6hs7lgzu44_a3d48bf8de084ffb9c5b0d7a1d82bfe3

複数コストエリアグリッド経路計画

通行可能エリアの異なるエリアに異なる移動コストを設定した後、アルゴリズムは包括的なコストが最も低い経路を選択します。複数コストグリッド経路計画の適用を示すために、前のシナリオに高コスト移動エリアが追加されています。

  1. 高コスト移動エリアを表すグリッドを building_grid テーブルに挿入します。

    INSERT INTO building_grid (grid, grid_type)
    SELECT grid, 'risk_zone'
    FROM
      (SELECT unnest(ST_As3DGrid(ST_SetSrid('BOX3D(106.59192708333332 29.706614582520686 341.8505366235368, 106.59220486111111 29.706892360298472 365.4271128197744)' :: box3d :: meshgeom, 4490), 24)) grid) tmp;

    エリアの形状は、次の図(オレンジ色の部分)のようになります。

    image

    説明

    通行不可グリッドに重なる高コスト移動エリアグリッドも、通行不可グリッドとみなされます。

  2. 経路計画を実行します。

    WITH vals (start_p, end_p, box_range) AS (
      VALUES 
        (
          ST_PointZ(106.59182291666669, 29.70644097140958, 355.322855208642, 4490), 
          ST_PointZ(106.59244791666666, 29.707135415854022, 348.58669235736755, 4490), 
          'BOX3D(106.591788194444 29.7062326380763 341.85053662386713, 106.592899305556 29.7071701380762 362.0590251699541)' :: box3d
        )), 
    border AS (
      SELECT DISTINCT grid, grid_type 
      FROM building_grid, vals 
      WHERE 
        ST_3DIntersects(ST_SetSrid(box_range :: meshgeom, 4490), grid) 
        OR ST_3DContains(ST_SetSrid(box_range :: meshgeom, 4490), grid)
    ) 
    -- risk_zone の移動コストを 2 に設定します。
    SELECT ST_3DGridPath(start_p, end_p, box_range, array[ST_SetCost( array (SELECT grid FROM border WHERE grid_type = 'border' ), -1), ST_SetCost( array (SELECT grid FROM border WHERE grid_type = 'risk_zone' ), 2)], '{"algorithm":"astar","movement":"cross","distance":"euclidean"}') result 
    FROM vals;

    次の図は、結果の経路を示しています。

    image

    7image

    この経路を高コスト移動エリアのない経路(紫色の経路)と比較します。

    image

まとめ

GanosBase 地理グリッドエンジンを利用することで、最小限のコーディングで UAV 経路計画を実現できます。世界初のプロフェッショナル空間データベースである GanosBase は、空間データの範囲を空中、地上、屋内と屋外、地上と地下、動的と静的エリアにまで広げています。基盤となるデータベースシステムレベルから物理世界のデジタル化のための時空間処理フレームワークを提供します。今後、GanosBase は、より効率的なデータベース内空間分析および可視化機能を提供し続け、可視化と計算のシームレスな統合に向けて、さまざまな業界にわたる空間情報の適用を促進します。

付録

Node.js を使用してシンプルなバックエンドサービスを作成し、3DTiles リクエストをプロキシし、Cesium をフロントエンドフレームワークとして使用して結果を表示します。

  1. カスタムディレクトリに、依存関係ファイル package.json を記述します。

    {
      "dependencies": {
        "koa": "^2.14.2",
        "koa-router": "^12.0.0",
        "koa-send": "^5.0.1",
        "pg": "^8.10.0"
      }
    }
  2. ディレクトリで npm install を実行して依存関係をインストールします。同じディレクトリに Node.js スクリプトファイル index.js を作成します。

    const Koa = require('koa');
    const Send = require('koa-send');
    const Router = require('koa-router');
    const router = new Router()
    const { Pool } = require('pg');
    const pool = new Pool({ user: <YOUR_USER>, host: <YOUR_HOST>, database: <YOUR_DB_NAME>, port: <YOUR_PORT> });
    
    const TABLE_NAME = 'test_osgb'
    const PROJECT_NAME = 'prj1'
    
    router.get('/', (_) => Send(_, '/index.html'))
    
    /* 現在のプロジェクトのメタデータを取得します */
    router.get('/project', async (ctx) => {
      const sql = `SELECT (REGEXP_MATCHES(ST_ASTEXT(ST_TRANSFORM(ANCHOR,4326)), 'POINT Z \\((.*?)\\)'))[1] _ANCHOR, PROJECT_ID FROM ${TABLE_NAME} WHERE PROJECT_NAME='${PROJECT_NAME}';`
      const { rows: [{ _anchor, project_id }] } = await pool.query(sql)
      const anchor = _anchor.split(' ').map(x => parseFloat(x))
      const url = `/tileset/${project_id}/${project_id}`
      ctx.body = { url, anchor }
    })
    
    router.get('/tileset/:project_id/:uid', async (ctx) => {
      const { params: { project_id, uid } } = ctx
      const sql = `SELECT TILESET FROM ${TABLE_NAME}_TILESET WHERE PROJECT_ID=$1::UUID AND UID=$2::UUID;`
      const { rows: [{ tileset }] } = await pool.query(sql, [project_id, uid])
      ctx.body = tileset
    })
    
    router.get('/b3dm/:project_id/:uid', async (ctx) => {
      const { params: { project_id, uid } } = ctx
      const sql = `SELECT ST_ASB3DM(TILE) TILE FROM ${TABLE_NAME}_TILE WHERE PROJECT_ID=$1::UUID AND UID=$2::UUID;`
      const { rows: [{ tile }] } = await pool.query(sql, [project_id, uid])
      ctx.body = tile
    })
    
    new Koa().use(router.routes()).listen(5500, '0.0.0.0');
  3. 同じディレクトリに HTML ファイル index.html を作成します。

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
      <meta charset="utf-8">
      <script src="https://cesium.com/downloads/cesiumjs/releases/1.89/Build/Cesium/Cesium.js"></script>
      <link href="https://cesium.com/downloads/cesiumjs/releases/1.89/Build/Cesium/Widgets/widgets.css" rel="stylesheet">
      <style>
        html,
        body,
        #cesium_container {
          width: 100%;
          height: 100%;
          margin: 0;
          padding: 0;
          overflow: hidden;
        }
      </style>
    </head>
    
    <body>
      <div id="cesium_container"></div>
      <script>
        Cesium.Ion.defaultAccessToken = <SET_YOUR_OWN_TOKEN_HERE>
        const disable_opt = ['timeline', 'animation', 'infoBox', 'navigationHelpButton']
          .reduce((opts, opt) => (opts[opt] = false) || opts, {})
        const viewer = new Cesium.Viewer('cesium_container', disable_opt)
        fetch('/project')
          .then(res => res.json())
          .then(({ anchor, url }) => {
            const position = Cesium.Cartesian3.fromDegrees(...anchor)
            const modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(position)
            const tileset = new Cesium.Cesium3DTileset({ url, modelMatrix })
            tileset.readyPromise.then(() => viewer.zoomTo(tileset))
            viewer.scene.primitives.add(tileset)
          })
      </script>
      </div>
    </body>
    
    </html>
  4. 同じディレクトリで以下のコマンドを実行してサービスを開始し、ブラウザに localhost: 5500 と入力してデータを表示します。

    node index.js