2025年10月29日水曜日

LiDARスキャナーで取得した点群データをデータベースに保存してthree.jsで表示する

iPhone 12以降のProモデルにはLiDARスキャナーが搭載されています。このLiDARスキャナーを使って取得した点群データをOracle Databaseに保存し、Three.jsを使って表示するAPEXアプリケーションを作成してみます。LiDARスキャンを実行するアプリケーションとして、Niantic Spatial社のScaniverseを使用します。

作成したアプリケーションは以下のように動作します。今回はズーム、回転および中心位置の移動に、ベルギーのUnited Codes社の製品Plug-Ins Proに含まれているUC - Range Sliderを使用しました。


ブラウザでの点群データの表示にthree.jsを使用しています。

最初はxeoglを使おうと考えていたのですが、Claudeによると「xeoglについては、ライブラリが古くなっていてCDNが利用できなくなっている可能性が高いです。xeoglプロジェクトは2017年頃から更新が停止しており、公式サイトやCDNへのアクセスができない状態になっています。」とのことでした。Claudeの回答の「公式サイトやCDNへのアクセスができない」という部分は正しいとは言えませんが、GitHubのリポジトリを確認する範囲では「xeoglプロジェクトは2017年頃から更新が停止しており」の部分は正しそうです。

そして、Claudeから次の提案がされました。「xeoglライブラリが廃止されている可能性があるため、three.jsを使った代替案を作成しましょうか?three.jsは現在も活発にメンテナンスされており、点群表示に最適です。」

Claudeの提案に従って、three.jsを使った点群データのビューワー作成してもらいました。APEXアプリケーションは、Claudeが作成したindex.htmlpointcloud.jsをもとに作成しています。私のthree.jsや3Dレンダリングに関する知見が乏しいため、その部分については、Claudeが生成したコードをほぼそのまま採用しています。

以下より、APEXアプリケーションの作成作業を記述します。

最初にiPhoneのLiDARスキャナーで、物体の点群データを取得します。

スマホでScaniverseを起動し、プラスボタンをクリックして撮影を開始します。


取得したLiDARデータは点群データとしてエクスポートするため、メッシュを選択します。


今回は小さなオブジェクトを選択して、スキャンを実施しています。作成するAPEXアプリケーションにはズーム、中心位置X、Y、Zを変更するスライダーを設置していますが、範囲が固定値で小さなオブジェクト向けになっています。ミディアムや大きなオブジェクトの場合は範囲を調整する必要があるでしょう。


LiDARスキャナーの準備が完了します。赤いボタンをクリックしてスキャンを開始します。


画面に表示されるガイドに従って、対象オブジェクトのスキャンを実行します。完了したら、スキャンを停止します。


スキャンを停止したあとに処理モードを選択します。今回はエリアを選択しました。


以上でスキャンは完了です。データを保存します。


iPhoneに保存されているLiDARデータをOracle Databaseにロードできるように、エクスポートします。

データはエクスポートする前に編集できます。とくにトリミングでは、トップフロントを指定して、周辺の不要なデータを削除できます。


共有をクリックし、モデルのエクスポートを選択します。


 モデルをエクスポートするフォーマットを選択します。今回はPLY(Polygon File Format)を選択します。ジオリファレンス(緯度経度)が必要な場合はLASを選択します。


モデルのエクスポート先を選択します。PLYファイル(後でCSVに変換します)をデータベースにロードするためにSQLclを使用します。そのため、SQLclが実行できるPC上にファイルをエクスポートします。本作業ではAirDropを選択し、Macbookにエクスポートしています。


以上で、LiDARデータの取得は完了です。

続いてエクスポートしたPLYファイルに含まれる点群データを、Oracle Databaseにロードします。

作業はmacOSで実施します。PLYファイルに含まれる点群データをCSV形式に変換するために、PDALを使用します。PDALとSQLclが使用できる環境であれば、macOSでなくても同様に作業できるでしょう。

macOSではHomebrewでPDALをインストールできます。

brew install pdal

使用したPDALのバージョンは2.9.2です。

pdal --version

% pdal --version 

---------------------------------------------------------------------------------------------------------------------------

pdal 2.9.2 (git-version: Release)

---------------------------------------------------------------------------------------------------------------------------


% 


ScaniverseからエクスポートしたファイルがScaniverse 2025-10-28 094508.plyである場合、CSVへ変換するために以下のコマンドを実行します。数値はデフォルトで小数点以下3桁なので、8桁まで増やしています。
pdal translate "Scaniverse 2025-10-28 094508.ply" output.csv \
  --writers.text.order="X,Y,Z,Red,Green,Blue" \
  --writers.text.delimiter=',' \
  --writers.text.precision=8 \
  --writers.text.write_header=false

% pdal translate "Scaniverse 2025-10-28 094508.ply" output.csv \

  --writers.text.order="X,Y,Z,Red,Green,Blue" \

  --writers.text.delimiter=',' \

  --writers.text.precision=8 \

--writers.text.write_header=false

% 


CSVの列はX、Y、Z、R、G、Bの順で並ぶようにし、ヘッダー行は出力しません。変換されたoutput.csvは、以下のようになります。

-0.05524448,0.00925601,0.04967391,130.00000000,90.00000000,66.00000000

-0.05524448,0.00988102,0.04779890,122.00000000,82.00000000,58.00000000

-0.07461950,0.05925608,0.00717390,150.00000000,94.00000000,58.00000000

-0.05461952,0.01050603,0.04779890,106.00000000,86.00000000,70.00000000

-0.07524452,0.06113100,0.00654891,150.00000000,94.00000000,58.00000000

-0.05461952,0.00988102,0.04842392,118.00000000,86.00000000,66.00000000

-0.07461950,0.05988097,0.00717390,146.00000000,90.00000000,54.00000000


このCSV形式のデータを、SQLclを使用してOracle Databaseにロードします。

SQLclでAPEXのワークスペース・スキーマに接続します。最初にロード先となる表をMYAKU_POINTSとして作成します。
drop table if exists myaku_points;
create table myaku_points(
    x number,
    y number,
    z number,
    r number,
    g number,
    b number)
nologging;

% sql wksp_apexdev@localhost/freepdb1



SQLcl: 水 10月 29 13:09:35 2025のリリース25.2 Production


Copyright (c) 1982, 2025, Oracle.  All rights reserved.


パスワード (**********?) ******

接続先:

Oracle Database 23ai Free Release 23.0.0.0.0 - Develop, Learn, and Run for Free

Version 23.9.0.25.07


SQL> drop table if exists myaku_points;


Table MYAKU_POINTSが削除されました。


SQL> create table myaku_points(

  2      x number,

  3      y number,

  4      z number,

  5      r number,

  6      g number,

  7      b number)

  8* nologging;


Table MYAKU_POINTSは作成されました。


SQL> 


loadコマンドを使って、output.csvの内容を表MYAKU_POINTSにロードします。
set load batch_rows 10000
set loadformat column_names off
load myaku_points ./output.csv

SQL> set load batch_rows 10000

SQL> set loadformat column_names off

SQL> load myaku_points ./output.csv


csv

column_names off

delimiter ,

enclosures ""

double off

encoding UTF8

row_limit off

row_terminator default

skip_rows 0

skip_after_names


データを表にロードします WKSP_APEXDEV.MYAKU_POINTS

batch_rows 10000

batches_per_commit 10

clean_names transform

column_size rounded

commit on

date_format 

errors 50

map_column_names off

method insert

timestamp_format 

timestamptz_format 

locale

scan_rows 100

truncate off

unknown_columns_fail on


#INFO 処理された行数: 243,817

#INFO エラーのある行数: 0

#INFO 最後にコミットされたバッチで処理された最後の行: 243,817

成功: エラーなしで処理されました

SQL> exit

Oracle Database 23ai Free Release 23.0.0.0.0 - Develop, Learn, and Run for Free

Version 23.9.0.25.07から切断されました

% 


以上でiPhoneのLiDARスキャナーを使って取得した点群データを、Oracle Databaseに保存できました。

Oracle Databaseに保存した点群データを表示する、APEXアプリケーションを作成します。

空のAPEXアプリケーションを作成します。名前LiDARデータ・ビューワーとします。


APEXアプリケーションが作成されます。デフォルトで作成されるホーム・ページに、すべての機能を実装します。


ページ・アイテムでズーム、回転、中心の移動を実装するため、United Codes社のプラグインUC - Range Sliderを使用します。

United Codes社のPlug-ins Proのページを開きます。


開いたページを下にスクロールし、Our Plug-insのセクションを見つけます。

UC Range Sliderはアイテム・プラグインなのでItemを選択しても見つけられますが、無償で利用できることを確実に確認するため、Free!を選択します。Free!のカテゴリ内に、UC Range Sliderを見つけることができます。

UC Range Sliderをクリックして開きます。

開いたダイアログ上にあるLogin to Downloadをクリックし、プラグインをダウンロードします。Loginとあるように、無償利用できるプラグインでも、United Codes社にユーザー登録する必要があります。


Login to Downloadをクリックすると、ログイン画面が開きます。新規にユーザー登録をする場合は、Need a United Codes Account? Sign-up now.をクリックします。

ユーザー登録の手順の説明は割愛します。


Plug-ins Proの画面にログインすると、ダッシュボードのページが開きます。検索フィールドRange Sliderと入力し、UC Range Sliderのプラグインを見つけます。


UC Range Sliderの概要がダイアログに表示されます。ダイアログ上にDownloadボタンがあります。

概要を読むと、このプラグインがnoUiSliderを使用していることが分かります。また、AuthorにFOSと記載されていますが、これはオーストリアにあったFOEX GmbHを指しています。FOEXは2022年3月にOracle Corporationによって買収されました。Oracle Corporationによる買収に際して、FOEXが開発していたFOEX Plugin FrameworkをUnited Codes社が引き継いでいます。

DownloadをクリックするとUC_Range_Slider_v25.1.zipが手元にダウンロードされます。ファイル名のバージョン番号は、将来変更されるでしょう。


UC Range Sliderのドキュメンテーションへのリンクは以下です。


ファイルUC_Range_Slider_v25.1.zipには、README.mdとSQLファイルが2つ含まれています。
  • app_unitedcodes_free_plugins.sql - サンプル・アプリケーションのエクスポート
  • item_type_plugin_uc_range_slider.sql - プラグインのエクスポート
プラグインのエクスポートはitem_type_plugin_uc_range_slider.sqlなので、このファイルをAPEXアプリケーションにインポートします。

APEXのアプリケーション・ビルダーに戻り、共有コンポーネントプラグインを開きます。


作成済みのプラグインの一覧画面から、インポートをクリックします。


インポートするファイルとしてitem_type_plugin_uc_range_slider.sqlを選択します。ファイル・タイププラグインです。

へ進みます。


ファイルのインポートが完了します。すぐにプラグインをインストールするため、へ進みます。


確認画面が開きます。プラグインのインストールをクリックします。


プラグインの一覧画面に戻ります。一覧にUC - Range Sliderが表示されます。


以上で、スライダーに使用するプラグインのインストールは完了です。

ページ・デザイナでホーム・ページを開き、コンポーネントを配置します。

ボタンやスライダーといった、操作を行うコンポーネントを配置するリージョンを作成します。識別名前Controlsタイプ静的コンテンツとします。

外観テンプレートには装飾のないBlack with Attributesを選択します。

このリージョンに配置するコンポーネントから呼び出される処理は、APEXアクションとして静的アプリケーション・ファイルに記述します。APEXアクションのコンテキストをこのリージョンに作成するため、詳細静的IDとしてCONTROLSを設定します。


three.jsが点群を描画するリージョンを作成します。

識別名前Canvasタイプ静的コンテンツとします。ソースHTMLコードに以下を記述します。このDIV要素の子要素として点群を描画するキャンバス要素が作成されます。

<div id="canvas-container"></div>

外観テンプレートに装飾のないBlank with Attributesを選択します。


ページ・プロパティCSSインラインに以下を記述し、リージョンのサイズを指定します。
横幅は(親となっている要素と同じ)100%、高さは1000pxとしています。
#canvas-container {
    flex: 1;
    width: 100%;
    height: 1000px;
    position: relative;
}

リージョンControlsに操作を行うコンポーネントを作成します。

データベースから点群データを読み込むボタンGENERATEを作成します。ラベル点群データの読み込みとします。

レイアウトリージョンControlsスロットPreviousを選択し、リージョンControlsの上部にボタンを配置します。外観ホットオンテンプレート・オプションWidthStretchを選択します。

ボタンをクリックしたときの処理は、静的アプリケーション・ファイルにAPEXアクションGENERATEとして記述します。そのため、動作アクション動的アクションで定義を選択し、詳細カスタム属性data-action="GENERATE"を記述します。


ページ・アイテムP1_ZOOMとして、ズームを操作するスライダーを設置します。タイプとしてUC - Range Sliderを選択します。ラベルズームです。

設定タイプNumberHandlesSingle(単一の値 - Doubleは上限と下限の範囲)、Minimum Value0Maximum Value5Number Steps0.01OrientationHorizontalを指定します。

水平のスライダーを使って、0から5の間の値を0.01刻みで設定できます。

OptionsEnable Ticks(スライダーに目盛を表示)、Enable Tooltips(ツールチップとして現在の値を表示)、Connect Handles(スライダーの色付け)にチェックを入れます。

Ticks(スライダーの範囲は0から5なので、0,1,2,3,4,5の6つの値が目盛に表示される)、Range Color#0076dfを設定し、選択された部分を青で表示します。


レイアウトリージョンControlsスロットRegion Bodyを指定します。スライダーをリージョンの左端に配置する場合は、新規行の開始オンにします。すでに存在するスライダーの右隣に配置する場合は、新規行の開始オフにします。

スライダーの場合、外観テンプレートOptional - Above(もしくはRequired - Above)を選択します。Floatingを選択するとラベルがスライダに被るため、見た目が良くありません。

ズームのステップを0.01としているため、ツールチップとして表示される数値も小数点以下2桁まで表示するように設定します。デフォルトでは小数点以下の値は表示されません。

詳細初期化JavaScriptファンクションに以下を記述します。
function(config) {
    config.numberFormat = {
        "decimals": 2
    };
    return config;
}

スライダーによって変更した値をカメラに反映させるために、動的アクションを作成します。

作成したページ・アイテムに動的アクションを作成します。タイミングイベントは標準の変更の代わりに、UC - Range Slider - Change [UC - Range Slider]を選択します。標準のページ・アイテムの変更イベントと同様に、値が変更されたときに発生するイベントになります。


スライダーの値が変更されたとき(より正確には、スライダーを動かしているときではなく、スライダーを止めて値が確定したとき)に実行するTRUEアクションとしてJavaScriptコードの実行を選択し、設定コードに以下を記述します。
apex.actions.findContextById("CONTROLS").invoke("ZOOM", this.browserEvent, this.triggeringElement);
リージョンControlsに作成したAPEXアクションZOOMを呼び出します。


以上でズームのスライダーが設定できました。

この他のスライダーも同様の手順で作成します。

ページ・アイテムP1_ROTATION_Hとして水平回転のスライダーを作成します。

設定Minimum Value0Maximum Value360Number Steps1とします。OptionsEnable TicksEnable Tooltipsにチェックを入れ、Ticks(0, 90, 180, 270, 360の5つが目盛に表示)を設定します。

数値の後ろに°(角度の単位)を追加するため、詳細初期化JavaScriptファンクションに以下を記述します。
function(config) {
    config.numberFormat = {
        "postfix": "°"
    };
    return config;
}
スライダーの値が変更されたときにAPEXアクションROTATION_Hを呼び出すように、動的アクションを作成します。


ページ・アイテムP1_ROTATION_Vとして垂直回転のスライダーを作成します。設定は水平回転のスライダーとほぼ同じですが、Minimum Value-90Maxmum Value90に設定します。値が変更されたときに呼び出されるAPEXアクションはROTATION_Vです。


ページ・アイテムP1_CENTER_Xとして、X軸の中心を移動するスライダーを作成します。ラベル中心 Xとします。

設定Minimum Value-2Maximum Value2Number Steps0.01を設定します。この値は、今回データベースにロードした点群データが見やすくなるように設定した値なので、ロードするデータに合わせて調整が必要かもしれません。ロードしたデータの範囲を確認して動的に変更することも可能ですが、今回はそこまでの実装はしていません。

ツールチップに小数点以下2桁まで表示するため、詳細初期化JavaScriptファンクションズームと同じスクリプトを記述します。値が変更されたときに呼び出されるAPEXアクションはCENTER_Xです。


ページ・アイテムP1_CENTER_Yとして、Y軸の中心を移動するスライダーを作成します。ラベル中心 Yとします。設定はX軸の中心を移動するスライダーとほぼ同じです。値が変更されたときに呼び出されるAPEXアクションはCENTER_Yです。


ページ・アイテムP1_CENTER_Zとして、Z軸の中心を移動するスライダーを作成します。ラベル中心 Zとします。設定はX軸の中心を移動するスライダーとほぼ同じです。値が変更されたときに呼び出されるAPEXアクションはCENTER_Zです。


以上で、点群データの表示をコントロールするボタンとスライダーの配置は完了です。

この時点でAPEXアプリケーションを実行すると、ホーム・ページは以下のように表示されます。


APEXアプリケーションに、実際の処理を実装します。

データベースから点群データを取り出すAjaxコールバックを作成します。

識別名前GET_POINTSソースPL/SQLコードに以下を記述します。


このAjaxコールバックは、ボタンGENERATEをクリックしたときに呼び出されるAPEXアクションGENERATEの中から呼び出されます。

点群データに加えて、X,Y,Z方向の最小値、最大値も属性extentとして返却しているため、これらの値を使ってスライダーの範囲を動的に変える実装も可能でしょう。


点群データの表示を実行するJavaScriptコードを記述した、静的アプリケーション・ファイルを作成します。

ファイル名はpointcloud.jsとします。記述するコードは以下です。



作成した静的アプリケーション・ファイルpointcloud.jsを、ホーム・ページで実行されるように設定します。

three.jsをJavaScriptから参照しやすくなるように、ページ・プロパティHTMLヘッダーにimportmapを定義します。
<script type="importmap">
  {
    "imports": {
      "three": "https://cdn.jsdelivr.net/npm/three@0.180.0/build/three.module.js",
      "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.180.0/examples/jsm/"
    }
  }
</script>

JavaScriptファイルURLに、静的アプリケーション・ファイルpointcloud.jsを参照するように以下を記述します。

[module]#APP_FILES#pointcloud#MIN#.js


以上でAPEXアプリケーションは完成です。アプリケーションを実行すると、記事の先頭のGIF動画のように動作します。

今回作成したAPEXアプリケーションのエクスポートを以下に置きました。
https://github.com/ujnak/apexapps/blob/master/exports/lidar-data-viewer.zip

点群データを表示するためにScaniverseで取得したPLYファイルをCSVに変換してデータベースにロードしていますが、その変換やthree.jsでの表示が適切かどうか確認するためにPLYファイルをインポートして表示できるMeshLabを使用しました。

MeshLabによる描画とthree.jsでの描画を比較することにより、APEXアプリケーションの動作を確認できます。


今回の記事は以上になります。

Oracle APEXアプリケーション作成の参考になれば幸いです。