[Astro] #115 Point Cloud Viewer #10 — LAZ圧縮エクスポート・ファイル名反映・進捗表示
概要
前回[#114]でOctree全点LODとWorker化を実装したが、エクスポートはLAS(無圧縮)のみだった。今回は自作WASMでLAZ圧縮エクスポートを実現し、エクスポート周りのUX改善も行った。
[Astro] #114 Point Cloud Viewer #9 — Octree全点LOD・Worker化・カメラ追従更新
全点Octree構築+カメラ距離ベースLOD、Worker化。
lain-lab.com今回の追加機能
| 機能 | 概要 |
|---|---|
| LAZエクスポート | カスタムlaz-perf WASM(250KB)でLAS→LAZ圧縮出力 |
| ファイル名反映 | 全エクスポートのファイル名をソースファイル名ベースに変更 |
| 圧縮進捗表示 | LAZ圧縮中にスピナー+パーセント表示、Cancelボタン付き |
背景 — LAZ圧縮はなぜ難しかったか
Point Cloud ViewerではLAZ読み込み(解凍)はlaz-perfのnpmパッケージで対応済みだった。しかし書き出し(圧縮)のEmscriptenバインディングはnpm版に存在しない。
当初は圧縮用WASMのサイズが大きくViewer同梱は重いと判断して見送っていたが、今回C++ソースに直接バインディングを追加してカスタムWASMをビルドしたところ、250KBに収まった。既存の解凍専用laz-perf.js(88KB)と大差なく、Viewerに同梱する障害がなくなった。
WASMビルドの詳細は別記事にまとめている。
[JavaScript] 極小250KBのLAZ圧縮Wasmを自作してブラウザでLAS→LAZ変換を動かす
laz-perfのEmscriptenカスタムビルド手順。
lain-lab.comLAZエクスポートの実装
スクリーンショット
方針 — 既存LASエクスポートへの最小差分
既存の handleExportLAS はLAS 1.2バイナリをメモリ上で組み立ててBlobダウンロードする処理。ここに asLAZ フラグを追加し、LASバイナリ組み立て後にMemFileWriterで圧縮をかける分岐を入れた。
const handleExportLAS = useCallback(async (asLAZ = false) => {
// ... 既存のLASバイナリ組み立て(変更なし)...
if (asLAZ) {
downloadData = await compressLAStoLAZ(buffer, (percent) => {
setLoading(`LAZ compressing... ${percent}%`);
});
}
const blob = new Blob([downloadData], { type: 'application/octet-stream' });
// ...
}, [sourceCRS, exportCRS, files, getExportName]);
compressLAZ.ts
LASバッファを受け取り、ヘッダーからポイントフォーマット・スケール・オフセットを読み取ってMemFileWriterに1点ずつ流し込む。UIフリーズを防ぐため、1万点ごとに setTimeout(0) でUIスレッドに制御を返す。
const CHUNK_SIZE = 10000;
for (let i = 0; i < pointCount; i++) {
LazPerf.HEAPU8.set(
lasData.subarray(srcOffset, srcOffset + pointRecordLength),
pointBuf
);
writer.writePoint(pointBuf);
if (i % CHUNK_SIZE === 0) {
onProgress?.(Math.floor((i / pointCount) * 100));
await yieldToUI();
}
}
圧縮率
398MBのLASファイル(1230万点、Point Format 2)→ 72.6MBのLAZ。**圧縮率18.2%**で、LAZの典型的な範囲(7-20%)に収まっている。
ControlPanelの変更
EXPORTセクションにLAZボタンを追加。グリッド3列で PLY/GLB/LAS/LAZ/PCD/XYZ/CSV の7ボタンになった。
<button style={btnStyle} onClick={onExportLAS}>LAS</button>
<button style={btnStyle} onClick={onExportLAZ}>LAZ</button>
エクスポートファイル名の改善
全エクスポート形式のファイル名を export.xxx 固定から、ソースファイル名ベースに変更した。
const getExportName = useCallback((ext: string) => {
if (files.length === 0) return `export.${ext}`;
const base = files[0].name.replace(/\.[^.]+$/, '');
return files.length === 1 ? `${base}.${ext}` : `${base}_merged.${ext}`;
}, [files]);
- 単一ファイル:
08NF2340.las→08NF2340.laz - 複数ファイル:
08NF2340_merged.laz
圧縮進捗表示
既存のローディングUI(スピナー+メッセージ+Cancelボタン)をそのまま流用。setLoading に「LAZ compressing… 20%」のようなメッセージを渡すだけで、解凍時と同じ表示が出る。
if (asLAZ) {
try {
setIsProcessing(true);
downloadData = await compressLAStoLAZ(buffer, (percent) => {
setLoading(`LAZ compressing... ${percent}%`);
});
} finally {
setIsProcessing(false);
setLoading(null);
}
}
変更ファイル
| ファイル | 変更内容 |
|---|---|
| compressLAZ.ts | 新規 — LAS→LAZ圧縮ラッパー(MemFileWriter + yieldToUI) |
| laz-perf.js | 差し替え — カスタムビルド(解凍+圧縮対応) |
| laz-perf.wasm | 差し替え — カスタムビルド(250KB) |
| PointCloudApp.tsx | handleExportLASにasLAZ分岐、getExportNameヘルパー追加、全エクスポートのファイル名変更 |
| ControlPanel.tsx | onExportLAZ props追加、LAZボタン追加 |
まとめ
- v1: PLY読み込み、ヒートマップ、スライス、計測、SOR
- v2: LAS/PCD読み込み、200万点サンプリング、正規化
- v3: LAZ読み込み、XYZ/CSV入出力、ローディング
- v4: Intensity/Classification表示、LAS/PCD/スクショエクスポート
- v5: ボックス選択+削除、断面ポリライン+DXF、Octree LOD、座標系変換
- v6: 計測ラベル3D表示、元座標復元、Voxel Downsampling
- v7: 任意直線断面+DXF、SOR進捗表示
- v8: SOR高速化+Worker化、Voxel Worker化、Delaunayメッシュ生成
- v9: Octree全点LOD(1230万点対応)、Worker化、カメラ追従更新、非LOD上限5M
- v10: LAZ圧縮エクスポート(自作WASM 250KB)、ファイル名反映、進捗表示
今後の課題
- Potree形式読み込み(Octreeタイルストリーミング)
- Poisson Reconstruction(3Dサーフェス復元)
- GPU Octree(WebGPU compute shader)
- VLRメタデータ保持(元LASの座標系情報引き継ぎ)