大規模な点群データ(LiDAR/LAZ)をWebブラウザで表示しようとすると、メモリ不足でクラッシュしてしまうことがあります。本記事では、Potree のLOD(Level of Detail)技術を使って、数百万点の点群をストレスなく表示する方法を紹介します。

https://3dtiles-viewer.vercel.app/potree-lod-viewer.html

使用データ

課題

このデータをそのままThree.jsなどで読み込もうとすると、ブラウザがフリーズする可能性があります。

解決策:Potree

Potreeは、大規模点群データのためのWebGLベースのビューアです。**LOD(Level of Detail)**により、カメラに近い部分は詳細に、遠い部分は粗く表示することで、数十億点のデータでもスムーズに動作します。

仕組み

  1. 点群をオクトリー構造 で空間分割
  2. 各ノードに異なる詳細度のデータを格納
  3. 視点に応じて必要なノードのみ動的に読み込み

手順

1. LAZファイルのダウンロード

curl -L -o utah_capitol.laz \
  "https://object.cloud.sdsc.edu/v1/AUTH_opentopography/www/education/MatlabTopo/Utah_state_capitol.laz"

2. LAZからPotree形式への変換

PotreeConverterをDockerで実行します。

# Dockerイメージの取得
docker pull synth3d/potreeconverter

# 変換実行
docker run --rm \
  -v $(pwd):/data \
  -v $(pwd)/output:/output \
  synth3d/potreeconverter \
  PotreeConverter /data/utah_capitol.laz -o /output/utah_capitol

出力結果 :

AABB:
min: [424889, 4.51427e+06, 1350.42]
max: [425270, 4.51471e+06, 1458.57]

spacing calculated from diagonal: 3.78973
conversion finished
3481512 points were processed and 3481512 points (100%) were written to the output.
duration: 1.819s

変換後のサイズ : 54MB

生成されるファイル構造:

utah_capitol/
├── cloud.js          # メタデータ
├── sources.json
└── data/
    └── r/
        ├── r.bin     # ルートノード
        ├── r.hrc     # 階層情報
        └── ...       # 多数のノード

3. Potreeライブラリの準備

Potree 1.8をダウンロードして配置します。

# ダウンロード
curl -L -o potree-1.8.zip \
  https://github.com/potree/potree/releases/download/1.8/Potree_1.8.zip

# 解凍して配置
unzip potree-1.8.zip
mv Potree_1.8 js/libs/

# 不要なフォルダを削除(サイズ削減: 121MB → 64MB)
rm -rf js/libs/Potree_1.8/pointclouds
rm -rf js/libs/Potree_1.8/examples
rm -rf js/libs/Potree_1.8/docs

4. ビューアHTMLの作成

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>Point Cloud Viewer (Potree)</title>

  <!-- Potree dependencies -->
  <script src="js/libs/Potree_1.8/libs/jquery/jquery-3.1.1.min.js"></script>
  <script src="js/libs/Potree_1.8/libs/spectrum/spectrum.js"></script>
  <script src="js/libs/Potree_1.8/libs/jquery-ui/jquery-ui.min.js"></script>
  <script src="js/libs/Potree_1.8/libs/three.js/build/three.min.js"></script>
  <script src="js/libs/Potree_1.8/libs/other/BinaryHeap.js"></script>
  <script src="js/libs/Potree_1.8/libs/tween/tween.min.js"></script>
  <script src="js/libs/Potree_1.8/libs/d3/d3.js"></script>
  <script src="js/libs/Potree_1.8/libs/proj4/proj4.js"></script>
  <script src="js/libs/Potree_1.8/libs/i18next/i18next.js"></script>
  <script src="js/libs/Potree_1.8/libs/jstree/jstree.js"></script>
  <script src="js/libs/Potree_1.8/build/potree/potree.js"></script>

  <!-- Potree CSS -->
  <link rel="stylesheet" href="js/libs/Potree_1.8/libs/spectrum/spectrum.css">
  <link rel="stylesheet" href="js/libs/Potree_1.8/libs/jquery-ui/jquery-ui.min.css">
  <link rel="stylesheet" href="js/libs/Potree_1.8/libs/jstree/themes/mixed/style.css">
  <link rel="stylesheet" href="js/libs/Potree_1.8/build/potree/potree.css">

  <style>
    html, body { width: 100%; height: 100%; margin: 0; overflow: hidden; }
    #potree_container { position: absolute; width: 100%; height: 100%; }
  </style>
</head>
<body>
  <div id="potree_container"></div>

  <script>
    // ビューア初期化
    const viewer = new Potree.Viewer(document.getElementById("potree_container"));
    viewer.setEDLEnabled(true);           // Eye-Dome Lighting
    viewer.setFOV(60);
    viewer.setPointBudget(3_000_000);     // 一度に表示する最大点数
    viewer.setBackground("gradient");

    // URLパラメータからデータパスを取得
    const params = new URLSearchParams(window.location.search);
    const dataPath = params.get('data') || 'files/utah_capitol/cloud.js';

    // 点群を読み込み
    Potree.loadPointCloud(dataPath, "pointcloud", function(e) {
      const pointcloud = e.pointcloud;

      // マテリアル設定
      pointcloud.material.size = 1;
      pointcloud.material.pointSizeType = Potree.PointSizeType.ADAPTIVE;
      pointcloud.material.shape = Potree.PointShape.CIRCLE;

      viewer.scene.addPointCloud(pointcloud);
      viewer.fitToScreen();
    });
  </script>
</body>
</html>

5. デプロイ

GitHubにpushしてVercel/GitHub Pagesで公開します。

git add docs/potree-lod-viewer.html docs/js/libs/Potree_1.8 docs/files/utah_capitol
git commit -m "Add Potree LOD point cloud viewer with Utah Capitol data"
git push origin main

結果

デモURL : https://3dtiles-viewer.vercel.app/potree-lod-viewer.html?data=files/utah_capitol/cloud.js

項目
データ出典OpenTopography
LAZサイズ15MB
変換後サイズ54MB
総点数3,481,512
Point Budget3,000,000
動作スムーズ

カメラをズームすると、自動的に詳細なノードが読み込まれます。

パフォーマンスのポイント

  1. Point Budget : viewer.setPointBudget(3_000_000) で一度に描画する点数を制限
  2. Adaptive Point Size : 距離に応じてポイントサイズを自動調整
  3. EDL (Eye-Dome Lighting) : 深度感を強調して視認性向上

IIIF Presentation API 3.0 との統合

点群データにメタデータ(タイトル、出典、ライセンスなど)を付与するため、IIIF Presentation API 3.0を活用します。

IIIFマニフェストの作成

{
  "@context": [
    "http://iiif.io/api/presentation/3/context.json",
    {
      "potree": "https://potree.github.io/ns#",
      "cloudUrl": "potree:cloudUrl",
      "pointCount": "potree:pointCount"
    }
  ],
  "id": "https://example.com/iiif/utah_capitol/manifest.json",
  "type": "Manifest",
  "label": {
    "en": ["Utah State Capitol"],
    "ja": ["ユタ州議事堂"]
  },
  "summary": {
    "en": ["LiDAR point cloud of the Utah State Capitol building"],
    "ja": ["ユタ州議事堂のLiDAR点群データ"]
  },
  "metadata": [
    {
      "label": { "en": ["Format"] },
      "value": { "en": ["Potree 1.7"] }
    },
    {
      "label": { "en": ["Point Count"] },
      "value": { "en": ["3,481,512"] }
    },
    {
      "label": { "en": ["Location"] },
      "value": { "en": ["Salt Lake City, Utah, USA"] }
    }
  ],
  "rights": "https://creativecommons.org/licenses/by/4.0/",
  "requiredStatement": {
    "label": { "en": ["Attribution"] },
    "value": { "en": ["Data provided by OpenTopography"] }
  },
  "provider": [
    {
      "id": "https://opentopography.org/",
      "type": "Agent",
      "label": { "en": ["OpenTopography"] },
      "homepage": [
        {
          "id": "https://opentopography.org/",
          "type": "Text",
          "format": "text/html"
        }
      ]
    }
  ],
  "items": [
    {
      "id": "https://example.com/iiif/utah_capitol/manifest.json/scene/1",
      "type": "Scene",
      "items": [
        {
          "id": "https://example.com/iiif/utah_capitol/manifest.json/annotation-page/1",
          "type": "AnnotationPage",
          "items": [
            {
              "id": "https://example.com/iiif/utah_capitol/manifest.json/annotation/1",
              "type": "Annotation",
              "motivation": "painting",
              "body": {
                "id": "https://example.com/files/utah_capitol/cloud.js",
                "type": "Model",
                "format": "application/json",
                "cloudUrl": "https://example.com/files/utah_capitol/cloud.js",
                "pointCount": 3481512
              },
              "target": "https://example.com/iiif/utah_capitol/manifest.json/scene/1"
            }
          ]
        }
      ]
    }
  ]
}

マニフェストの主要プロパティ

プロパティ説明
labelタイトル(多言語対応)
summary説明文
metadata形式、点数、位置などのメタデータ
rightsライセンスURL
requiredStatement必須の帰属表示
providerデータ提供者情報
items[].body.cloudUrlPotree cloud.jsへのURL

ビューアでのマニフェスト読み込み

// マニフェストURLからメタデータを取得
const manifestUrl = 'iiif/utah_capitol/manifest.json';

fetch(manifestUrl)
  .then(res => res.json())
  .then(manifest => {
    // タイトル表示
    const title = manifest.label?.ja?.[0] || manifest.label?.en?.[0];
    document.getElementById('title').textContent = title;

    // ライセンス表示
    if (manifest.rights) {
      const licenseName = manifest.rights.includes('by/4.0') ? 'CC BY 4.0' : 'License';
      document.getElementById('license').innerHTML =
        `<a href="${manifest.rights}">${licenseName}</a>`;
    }

    // 点群データのURL取得
    const cloudUrl = manifest.items[0].items[0].items[0].body.cloudUrl;
    loadPointCloud(cloudUrl);
  });

使用方法

# マニフェストから読み込み(メタデータ表示あり)
potree-lod-viewer.html?manifest=iiif/utah_capitol/manifest.json

# 直接データを指定(メタデータなし)
potree-lod-viewer.html?data=files/utah_capitol/cloud.js

IIIFを使うメリット

  1. 標準化されたメタデータ形式 - 他のIIIF対応ツールとの互換性
  2. 多言語対応 - labelsummaryで複数言語をサポート
  3. ライセンス・帰属の明示 - rightsrequiredStatementで法的情報を提供
  4. データ提供者情報 - providerでデータの出典を明確化
  5. 拡張性 - カスタムコンテキストでPotree固有のプロパティを追加可能

まとめ

  • 大規模点群の表示にはLOD が必須
  • PotreeConverter (Docker)で簡単に変換可能
  • Potree 1.8 で数百万点以上もスムーズに表示
  • IIIF Presentation API 3.0 でメタデータを標準化
  • 追加データは ?manifest=path/to/manifest.json で読み込み可能

参考リンク