2023年3月8日水曜日

PLATEAU-3DTilesをCesium.jsを使って表示する

 PLATEAU-3DTiles配信チュートリアル3.1. Cesium.jsでの利用方法に記載されている実装をOracle APEXのアプリケーションに組み込み、建物データ(3D Tiles)をCesium.jsで表示してみます。

建物データの表示はチュートリアルをそのまま実装します。Oracle APEXを使って3D Tiles一覧のURLを置き換える機能を追加します。

アプリケーション作成ウィザードを起動し、空のアプリケーションを作成します。名前PLATEAU 3DTilesとします。Cesium.jsによる建物データの表示は、デフォルトで作成されるホーム・ページに実装します。

アプリケーションの作成をクリックします。

アプリケーションが作成されたら、ページ・デザイナホーム・ページを開きます。

Cesium.jsでの利用方法に記載されているサンプルコードを、ホーム・ページにそのまま埋め込みます。

ページ・プロパティJavaScriptファイルURLに以下を記載します。CesiumJSのバージョンは新しいものを使用します。

https://cesium.com/downloads/cesiumjs/releases/1.103/Build/Cesium/Cesium.js

ページ・ロード時に実行に、<script>...</script>の間に記述されているJavaScriptのコードを転記します。

CSSファイルURLに以下を記載します。

https://cesium.com/downloads/cesiumjs/releases/1.103/Build/Cesium/Widgets/widgets.css

CSSインラインに、<style>...</style>の間に記述されている#cesiumContainerのスタイル定義を転記します。

Cesium.jsを組み込むリージョンを作成します。

識別タイトル3D Tilesタイプ静的コンテンツを選択します。ソースHTMLコードとして以下を記述します。

<div id="cesiumContainer"></div>


外観テンプレートとしてStandardを選択します。不要な装飾を減らすために、テンプレート・オプションRemove Body Paddingチェックを入れ、HeaderHiddenStyleRemove UI Decorationを選択します。

Body Heightは、テンプレート・オプションとして選択できる最大の640pxを選択します。


今回の用途ではブレッドクラムは不要なので、削除します。


この時点でアプリケーションを実行すると、以下の画面が表示されます。


サンプルコードでは表示が千代田区に固定されています。

以下より別の地域も表示できるように、アプリケーションに機能を追加します。

3D Tiles一覧のURLを保存する表PLATEAU_3DTILESを作成します。

クイックSQLの以下のモデルを使用します。
# prefix: plateau
3dtiles
    citycode vc6 /nn
    type     vc8 /nn
    url      vc200 /nn
    lat      num
    lon      num
    has_coordinate vc1 /check Y,N
表の作成には、SQLワークショップユーティリティクイックSQLを使用します。


3DTiles一覧は、以下のJSONファイルとして提供されています。

https://raw.githubusercontent.com/Project-PLATEAU/plateau-streaming-tutorial/main/3dtiles_url.json

このファイルを読み込んで、表PLATEAU_3DTILESに保存します。

SQLコマンドで以下のコマンドを実行します。

declare
l_clob clob;
l_array json_array_t;
l_object json_object_t;
l_citycode plateau_3dtiles.citycode%type;
l_type plateau_3dtiles.type%type;
l_url plateau_3dtiles.url%type;
begin
l_clob := apex_web_service.make_rest_request(
p_url => 'https://raw.githubusercontent.com/Project-PLATEAU/plateau-streaming-tutorial/main/3dtiles_url.json'
,p_http_method => 'GET'
);
l_array := json_array_t.parse(l_clob);
for i in 0..(l_array.get_size()-1)
loop
l_object := treat(l_array.get(i) as json_object_t);
l_citycode := l_object.get_string('CITYCODE');
l_type := l_object.get_string('TYPE');
l_url := l_object.get_string('URL');
insert into plateau_3dtiles(citycode, type, url) values(l_citycode, l_type, l_url);
end loop;
end;
view raw 3dtiles_url.sql hosted with ❤ by GitHub
3dtiles_url.jsonは、CITYCODE、TYPE、URLの属性を含むオブジェクトの配列です。それぞれのオブジェクトを表PLATEAU_3DTILESの一行として保存しています。


3DTileのデータに座標値が含まれているか確認し、含まれている場合は中心の座標を列LATとLONに保存します。実際には空間の中心が市街地の中心とは限らないため、3DTilesを選択したときに、市街地の端が中心となって表示される場合はあります。

以下のSQLを実行します。SQLコマンドから大量のデータを一度に処理しようとすると、ブラウザがタイムアウトするため、件数を絞っています。繰り返し実行することで、すべてのデータを処理できます。

declare
l_clob clob;
l_tile json_object_t;
l_properties json_object_t;
l_x_lon json_object_t;
l_y_lat json_object_t;
l_x number;
l_y number;
l_count pls_integer;
begin
l_count := 0;
for r in (select id, url from plateau_3dtiles where type = 'bldg' and has_coordinate is null)
loop
/*
* タイムアウトを回避するために、少しづつ実施する。
*/
l_count := l_count + 1;
if l_count > 20 then
exit;
end if;
/*
* 3Dtileの内容から緯度経度を取り出す。
*/
l_clob := apex_web_service.make_rest_request(
p_url => r.url
,p_http_method => 'GET'
);
l_tile := json_object_t(l_clob);
l_properties := l_tile.get_object('properties');
if l_properties is not null then
l_x_lon := l_properties.get_object('_x');
l_y_lat := l_properties.get_object('_y');
if l_x_lon is not null and l_y_lat is not null then
l_x := (l_x_lon.get_number('minimum') + l_x_lon.get_number('maximum')) / 2;
l_y := (l_y_lat.get_number('minimum') + l_y_lat.get_number('maximum')) / 2;
update plateau_3dtiles set lat = l_y, lon = l_x, has_coordinate = 'Y' where id = r.id;
continue;
end if;
end if;
update plateau_urls set has_coordinate = 'N' where id = r.id;
end loop;
/* 処理が必要な残りがあるかどうか確認する。 */
select count(*) into l_count from plateau_3dtiles where type = 'bldg' and has_coordinate is null;
dbms_output.put_line('未処理 = ' || l_count);
end;


未処理件数が0になれば、表示する建物データを切り替えるためのデータの準備は完了です。

表示する建物データの選択に使用するLOVを作成します。

共有コンポーネントLOVを開きます。


作成済みのLOVの一覧が表示されます。作成をクリックします。


LOVの作成最初からを選択します。

へ進みます。


名前PLATEAU_3DTILESとします。タイプDynamicです。

へ進みます。


データ・ソース表/ビューの名前として、PLATEAU_3DTILES(表)を選択します。

へ進みます。


列マッピング戻り列表示列ともに、URLを選択します。

作成をクリックします。


LOVとしてPLATEAU_3DTILESが作成されます。このLOVに、追加値として列LATおよびLONを指定できるようにします。

PLATEAU_3DTILESをクリックして編集画面を開きます。


最初にソースWHERE句に以下を記述します。座標値が設定されているURLだけを選択対象にします。

has_coordinate = 'Y'

座標を求めているのはtypebldgである建物データに限っています。typeには他にfldtunamitakashioがあります。これらの座標値を3DTileのデータより求める方法が分からなかったので、座標値は設定されていません。3DTileには地方公共団体コードが含まれているため、そのコードを元に列LATおよびLONに座標値を設定できると思います。


列の選択をクリックし、追加表示列としてURLに加えてLATおよびLONを追加します。列URLは戻り値として設定されているため、デフォルトで非表示、検索対象外になってます。

列名URLヘッダーURLを設定し、表示可能検索可能を共にONに変更します。

以上で変更の適用をクリックします。


ホーム・ページに建物データを選択するページ・アイテムを作成します。

識別名前P1_3DTILESタイプとしてポップアップLOVを選択します。ラベル3D Tilesとします。

設定追加出力として以下を設定します。ページ・アイテムP1_LATおよびP1_LONは、この後に作成します。

LAT:P1_LAT,LON:P1_LON

検証必須の値ONLOVタイプとして共有コンポーネントを選択し、LOVに先ほど作成したPLATEAU_3DTILESを指定します。追加値の表示NULL値の表示ともにOFFとします。


追加値を保持するページ・アイテムP1_LATおよびP1_LONを作成します。

タイプ非表示設定保護された値OFFにします。ポップアップLOVによる追加値の設定は、動的アクションによるページ・アイテムの値の変更と同じ処理であるため、保護を外す必要があります。


建物データを変更したときにページを再描画するため、ページの送信を行います。この処理は動的アクションで実装します。

ページ・アイテムP1_3DTILES動的アクションを作成します。名前建物データの変更とします。タイミングはデフォルトでイベント変更選択タイプアイテムアイテムP1_3DTILESとなります。


TRUEアクションとしてページの送信を指定します。


作成したページ・アイテムを使って建物データを表示するように、ページ・ロード時に実行するJavaScriptを変更します。

// Cesium Ionの読み込み指定
Cesium.Ion.defaultAccessToken = "&G_TOKEN.";
// Terrainの指定(EGM96、国土数値情報5m標高から生成した全国の地形モデル、5m標高データが無い場所は10m標高で補完している)
var viewer = new Cesium.Viewer("cesiumContainer", {
terrainProvider: new Cesium.CesiumTerrainProvider({
url: Cesium.IonResource.fromAssetId(&G_ASSET_ID.)
})
});
/*
* デフォルトとして千代田区を選択する。
*/
if (!apex.items.P1_3DTILES.getValue()) {
const defaultUrl = "https://plateau.geospatial.jp/main/data/3d-tiles/bldg/13100_tokyo/13101_chiyoda-ku/notexture/tileset.json";
apex.items.P1_3DTILES.setValue(defaultUrl, defaultUrl, false);
apex.items.P1_LAT.setValue(35.6872261, null, true);
apex.items.P1_LON.setValue(139.75657399, null, true);
}
// 建物データ(3D Tiles)
var your_3d_tiles = viewer.scene.primitives.add(new Cesium.Cesium3DTileset({
url : apex.items.P1_3DTILES.getValue()
}));
// カメラの初期位置の指定
viewer.camera.setView({
destination : Cesium.Cartesian3.fromDegrees(
apex.items.P1_LON.getValue(),
apex.items.P1_LAT.getValue(),
5000.0)
});
サンプルコードにはPLATEAUオルソを読み込む処理が含まれていますが、現在はこのURLからの提供は止まっている、または、認証が必要になっているようなのでコードからは削除しています。また、Cesium Ionのデフォルト・アクセス・トークンとアセットIDはコードに含めず、置換文字列G_TOKENおよびG_ASSET_IDによる指定に変更しています。


アプリケーション定義置換文字列としてG_TOKENおよびG_ASSET_IDを設定します。


サンプルコードに記載されているデフォルト・アクセス・トークンの値およびアセットIDは、今のところ有効ですが、サンプルの確認以上の作業を行う場合は、Cesium ionにアカウントを登録し取得したアクセス・トークンとアセットIDに置き換えるべきだと思います。


登録するアセットが5GB未満までは無料で利用できるようです。詳細はCesium Ionのページを参照してください。フリー・トライアルについても紹介されています。

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

Cesium.jsをAPEXアプリケーションに組み込むことはできましたが、何か実用的なアプリにするにはCesium.js自体についてよく調べて必要がありそうです。

今回作成したアプリケーションのエクスポートを以下に置きました。アクセス・トークンおよびアセットIDの設定は含んでいません。
https://github.com/ujnak/apexapps/blob/master/exports/plateau-3dtiles.zip

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