[Astro] #111 Point Cloud Viewer v6 — 計測ラベル3D表示・元座標復元・Voxel Downsampling
概要
前回[#110]でボックス選択・断面ポリライン・Octree LOD・座標系変換を追加し、Point Cloud Viewerは「編集ツール」へと進化した。今回のv6では実用性を高める3機能を追加する。
[Astro] #110 Point Cloud Viewer v5 — ボックス選択・断面ポリライン・Octree LOD・座標系変換
矩形ボックス選択+削除、断面プロファイル生成+DXFエクスポート、Octree LOD、座標系選択+CRS変換を追加。
lain-lab.comv6 新機能
| 機能 | 概要 |
|---|---|
| 計測ラベル3D表示 | 距離・面積・点番号をSpriteで3D空間上に表示(スクショに映る) |
| 元座標オフセット保持 | LASの座標メタデータを保持し、エクスポート時に地理座標を復元 |
| Voxel Downsampling | グリッドベースの点群間引き(軽量化) |
スクリーンショット
計測ラベル3D表示
Voxel Downsampling
計測ラベル3D表示
課題:dreiのHtmlはスクショに映らない
v5の計測ツールは距離・面積をパネルに数値表示するだけだった。3D空間上にラベルを表示したいが、R3Fの@react-three/dreiが提供するHtmlコンポーネントはDOM要素として描画されるため、canvas.toDataURL()によるスクリーンショットには映らない。
報告書に「この寸法はXXm」と注記付きでスクショを貼りたい場合、これでは使えない。
解決:CanvasTexture + Sprite
Canvas2Dでテキストを描画し、THREE.CanvasTexture→THREE.SpriteMaterialで3Dシーンに直接レンダリングする方式に切り替えた。
function TextSprite({ text, position, color = '#ff0', scale = 0.012 }) {
const texture = useMemo(() => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d')!;
const fontSize = 48;
ctx.font = `bold ${fontSize}px monospace`;
const w = Math.ceil(ctx.measureText(text).width) + 16;
const h = fontSize + 16;
canvas.width = w;
canvas.height = h;
// 背景 + ボーダー
ctx.fillStyle = 'rgba(0, 0, 0, 0.85)';
ctx.roundRect(0, 0, w, h, 6);
ctx.fill();
ctx.strokeStyle = color;
ctx.lineWidth = 2;
ctx.roundRect(1, 1, w - 2, h - 2, 5);
ctx.stroke();
// テキスト
ctx.font = `bold ${fontSize}px monospace`;
ctx.fillStyle = color;
ctx.textBaseline = 'middle';
ctx.textAlign = 'center';
ctx.fillText(text, w / 2, h / 2);
const tex = new THREE.CanvasTexture(canvas);
tex.needsUpdate = true;
return { tex, aspect: w / h };
}, [text, color]);
return (
<sprite position={position} scale={[scale * texture.aspect, scale, 1]}>
<spriteMaterial map={texture.tex} transparent depthTest={false} />
</sprite>
);
}
ポイント:
depthTest: falseで常に前面表示(点群に埋もれない)useMemoでCanvas再生成を抑制- アスペクト比を自動計算してテキスト幅に合わせたスケーリング
- WebGLの一部なので
canvas.toDataURL()でスクショに映る
表示される情報
| ラベル | 色 | 位置 |
|---|---|---|
| P1, P2, P3 | マゼンタ | 各計測点の上 |
| 距離値 | 黄色 | 2点の中点 |
| 面積 S=値 | 緑 | 三角形の重心 |
元座標オフセット保持
問題:正規化で地理座標が失われる
LASファイルの座標は測地座標系の絶対値(例: X=139456.789, Y=35012.345)。ブラウザでの3D表示のために原点にセンタリングし、最大寸法を1.0にスケーリングしている。
この正規化は表示には必要だが、エクスポート時に元の座標が復元できなかった。v5で追加したCRS変換(proj4)も、入力が正規化座標では実測値としての意味がなかった。
解決:LASOriginMeta
parseLASの戻り値にメタデータを追加。読み込み時の変換パラメータを保持する。
export interface LASOriginMeta {
centerX: number; centerY: number; centerZ: number;
normalizeScale: number;
scaleX: number; scaleY: number; scaleZ: number;
offsetX: number; offsetY: number; offsetZ: number;
}
エクスポート時はtoLAS()関数で正規化を逆変換し、元の地理座標を復元してからCRS変換を適用する。
const toLAS = (px, py, pz) => {
// Three.js → LAS座標(Y/Z swap)
let lasX = px, lasY = -pz, lasZ = py;
// 正規化逆変換
if (meta) {
lasX = lasX / meta.normalizeScale + meta.centerX;
lasY = lasY / meta.normalizeScale + meta.centerY;
lasZ = lasZ / meta.normalizeScale + meta.centerZ;
}
// CRS変換
if (sourceCRS !== exportCRS) {
[lasX, lasY, lasZ] = transformPointSync(lasX, lasY, lasZ, sourceCRS, exportCRS);
}
return [lasX, lasY, lasZ];
};
これによりLASファイルを読み込み→編集→LASエクスポートした際に、元の地理座標が正しく復元される。
Voxel Downsampling
仕組み
空間をvoxelSize単位のグリッドに分割し、各セル(ボクセル)から最初に見つかった1点だけを残す。SORノイズ除去で使った空間ハッシュグリッドと同じ原理。
const voxelMap = new Map<string, number>();
for (let i = 0; i < count; i++) {
const vx = Math.floor(positions.getX(i) / voxelSize);
const vy = Math.floor(positions.getY(i) / voxelSize);
const vz = Math.floor(positions.getZ(i) / voxelSize);
const key = `${vx},${vy},${vz}`;
if (!voxelMap.has(key)) {
voxelMap.set(key, i);
}
}
特徴
- 全属性保持: position, color, intensity, classificationの全てを維持
- 均一間引き: 密度が高い場所ほど多く間引かれ、結果的に均一な密度になる
- パラメータ: voxelSizeスライダー(0.001〜0.05)で間引き量を調整
- 実績: 200万点 → 17.4万点(91%削減)でもデータの形状は維持
用途
- データ軽量化: 大容量LiDARデータを扱いやすいサイズに縮小
- エクスポート前の前処理: 他ソフトに渡す前に点数を絞る
- メッシュ化の前処理: 点群からサーフェスを生成する前に密度を均一化
変更ファイル一覧
| ファイル | 変更内容 |
|---|---|
| parseLAS.ts | LASOriginMetaインターフェース追加、戻り値にoriginMeta含有 |
| PointCloudApp.tsx | originMetaRef管理、toLAS()で座標復元、VoxelDownsampleハンドラ |
| PointCloudScene.tsx | originMetaRef受け取り+格納、TextSpriteコンポーネント+計測ラベル |
| ControlPanel.tsx | VOXEL DOWNSAMPLEセクション追加(スライダー+ボタン) |
まとめ
v6の3機能はいずれも「実用性の底上げ」。
- 計測ラベルがスクショに映る → 報告書にそのまま使える
- 元座標が復元される → CRS変換が実測値で動く
- Voxel Downsampling → 巨大データの軽量化
開発経緯:
- v1: PLY読み込み、ヒートマップ、スライス、計測、SOR
- v2: LAS/PCD読み込み、200万点サンプリング、正規化
- v3: LAZ読み込み、XYZ/CSV入出力、ローディング
- v4: Intensity/Classification表示、LAS/PCD/スクショエクスポート
- v5: ボックス選択+削除、断面ポリライン+DXF、Octree LOD、座標系変換
- v6: 計測ラベル3D表示、元座標復元、Voxel Downsampling
今後の課題
- Octreeに全点(サンプリング前)を格納して大規模対応
- 任意角度の断面ポリライン
- Web Worker分離(パース・SOR・Downsamplingをバックグラウンド化)
- メッシュ化(Delaunay / Poisson Reconstruction)