[Astro] #111 Point Cloud Viewer v6 — 計測ラベル3D表示・元座標復元・Voxel Downsampling

[Astro] #111 Point Cloud Viewer v6 — 計測ラベル3D表示・元座標復元・Voxel Downsampling

概要

前回[#110]でボックス選択・断面ポリライン・Octree LOD・座標系変換を追加し、Point Cloud Viewerは「編集ツール」へと進化した。今回のv6では実用性を高める3機能を追加する。

v6 新機能

機能概要
計測ラベル3D表示距離・面積・点番号をSpriteで3D空間上に表示(スクショに映る)
元座標オフセット保持LASの座標メタデータを保持し、エクスポート時に地理座標を復元
Voxel Downsamplingグリッドベースの点群間引き(軽量化)

スクリーンショット

計測ラベル3D表示

Point Cloud Viewer — 計測ラベル3D表示 Point Cloud Viewer — 計測ラベル3D表示(スクリーンショット)

Voxel Downsampling

Point Cloud Viewer — Voxel Downsampling

計測ラベル3D表示

課題:dreiのHtmlはスクショに映らない

v5の計測ツールは距離・面積をパネルに数値表示するだけだった。3D空間上にラベルを表示したいが、R3Fの@react-three/dreiが提供するHtmlコンポーネントはDOM要素として描画されるため、canvas.toDataURL()によるスクリーンショットには映らない。

報告書に「この寸法はXXm」と注記付きでスクショを貼りたい場合、これでは使えない。

解決:CanvasTexture + Sprite

Canvas2Dでテキストを描画し、THREE.CanvasTextureTHREE.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.tsLASOriginMetaインターフェース追加、戻り値にoriginMeta含有
PointCloudApp.tsxoriginMetaRef管理、toLAS()で座標復元、VoxelDownsampleハンドラ
PointCloudScene.tsxoriginMetaRef受け取り+格納、TextSpriteコンポーネント+計測ラベル
ControlPanel.tsxVOXEL 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)