iPhone 12以降のProモデルにはLiDARスキャナーが搭載されています。このLiDARスキャナーを使って取得した点群データをOracle Databaseに保存し、Three.jsを使って表示するAPEXアプリケーションを作成してみます。LiDARスキャンを実行するアプリケーションとして、Niantic Spatial社の
Scaniverseを使用します。
ブラウザでの点群データの表示に
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.htmlと
pointcloud.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の上部にボタンを配置します。外観のホットをオン、テンプレート・オプションのWidthにStretchを選択します。
ボタンをクリックしたときの処理は、静的アプリケーション・ファイルにAPEXアクションGENERATEとして記述します。そのため、動作のアクションに動的アクションで定義を選択し、詳細のカスタム属性にdata-action="GENERATE"を記述します。
ページ・アイテムP1_ZOOMとして、ズームを操作するスライダーを設置します。タイプとしてUC - Range Sliderを選択します。ラベルはズームです。
設定のタイプはNumber、HandlesはSingle(単一の値 - Doubleは上限と下限の範囲)、Minimum Valueは0、Maximum Valueは5、Number Stepsは0.01、OrientationはHorizontalを指定します。
水平のスライダーを使って、0から5の間の値を0.01刻みで設定できます。
OptionsのEnable Ticks(スライダーに目盛を表示)、Enable Tooltips(ツールチップとして現在の値を表示)、Connect Handles(スライダーの色付け)にチェックを入れます。
Ticksに6(スライダーの範囲は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 Valueは0、Maximum Valueは360、Number Stepsは1とします。OptionsのEnable Ticks、Enable Tooltipsにチェックを入れ、Ticksに5(0, 90, 180, 270, 360の5つが目盛に表示)を設定します。
数値の後ろに°(角度の単位)を追加するため、詳細の初期化JavaScriptファンクションに以下を記述します。
function(config) {
config.numberFormat = {
"postfix": "°"
};
return config;
}
スライダーの値が変更されたときにAPEXアクションROTATION_Hを呼び出すように、動的アクションを作成します。

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

ページ・アイテムP1_CENTER_Xとして、X軸の中心を移動するスライダーを作成します。ラベルは中心 Xとします。
設定のMinimum Valueは-2、Maximum Valueは2、Number Stepsは0.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アプリケーション作成の参考になれば幸いです。
完