When trying to display large-scale point cloud data (LiDAR/LAZ) in a web browser, the browser may crash due to insufficient memory. This article introduces how to display millions of points without stress using Potree’s LOD (Level of Detail) technology.

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

Data Used

Challenge

Trying to load this data directly with Three.js or similar libraries may cause the browser to freeze.

Solution: Potree

Potree is a WebGL-based viewer for large-scale point cloud data. Through LOD (Level of Detail), it displays areas near the camera in detail and distant areas coarsely, enabling smooth operation even with billions of points.

How It Works

  1. Spatially divide the point cloud using an octree structure
  2. Store data at different levels of detail in each node
  3. Dynamically load only the necessary nodes based on the viewpoint

Procedure

1. Download the LAZ File

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

2. Convert LAZ to Potree Format

Run PotreeConverter using 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

Output:

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

Converted Size: 54MB

Generated file structure:

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

3. Prepare the Potree Library

Download and place 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. Create the Viewer 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. Deploy

Push to GitHub and publish on 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

Results

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

ItemValue
Data SourceOpenTopography
LAZ Size15MB
Converted Size54MB
Total Points3,481,512
Point Budget3,000,000
PerformanceSmooth

When zooming the camera, detailed nodes are automatically loaded.

Performance Tips

  1. Point Budget: Limit the number of points drawn at once with viewer.setPointBudget(3_000_000)
  2. Adaptive Point Size: Automatically adjust point size based on distance
  3. EDL (Eye-Dome Lighting): Enhance depth perception for improved visibility

Integration with IIIF Presentation API 3.0

To attach metadata (title, source, license, etc.) to point cloud data, we leverage IIIF Presentation API 3.0.

Creating the IIIF Manifest

{
  "@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"
            }
          ]
        }
      ]
    }
  ]
}

Key Manifest Properties

PropertyDescription
labelTitle (multilingual support)
summaryDescription text
metadataMetadata such as format, point count, location
rightsLicense URL
requiredStatementRequired attribution statement
providerData provider information
items[].body.cloudUrlURL to Potree cloud.js

Loading the Manifest in the Viewer

// マニフェスト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);
  });

Usage

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

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

Benefits of Using IIIF

  1. Standardized metadata format - Compatibility with other IIIF-compatible tools
  2. Multilingual support - Support for multiple languages in label and summary
  3. License and attribution clarity - Legal information provided through rights and requiredStatement
  4. Data provider information - Clear data provenance through provider
  5. Extensibility - Custom contexts allow adding Potree-specific properties

Summary

  • LOD is essential for displaying large-scale point clouds
  • PotreeConverter (Docker) makes conversion easy
  • Potree 1.8 smoothly displays millions of points or more
  • IIIF Presentation API 3.0 standardizes metadata
  • Additional data can be loaded via ?manifest=path/to/manifest.json