2021年11月5日金曜日

Oracle APEX 21.2新機能(11) - 地図関連の拡張

 Oracle APEX 21.2にて追加された地図に関連する機能は3つあります。

  1. ページ・アイテムタイプとしてマップの表示が追加されました。
  2. ミニマップというコンポーネントが使用できるようになりました。
  3. ページ・アイテムタイプとしてジオコーディングされた住所が追加されました。また、関連する動的アクションイベントとしてジオコーディング・レスポンス結果の選択が追加されています。
  4. 動的アクションアクションとしてジオコーディングのトリガーが追加されています。
残念なことにジオコーダーが対応する地域に日本は含まれていません。ですので、上記の3、4のジオコーディングに関する機能は、日本の住所については無効です。

以下はカード・リージョンとミニマップを組み合わせた実装例です。


新規に追加された地図の機能の実装することにより、それらの使い方を紹介します。

データの準備


アマノ技研さんより提供されているGeospatial Dataの地方公共団体の位置データをデータベースにロードし、役所の位置を地図上に表示します。


2021年11月2日時点ではasti-datr0304po.zipというファイルがダウンロードされました。このZIPファイルに含まれるr0304puboffice_utf8.csvをデータベースにロードします。

SQLワークショップユーティリティに含まれるデータ・ワークショップを開き、データのロードを実行します。


ファイルの選択をクリックし、アマノ技研さんよりダウンロードしたファイルr0304puboffice_utf8.csvをアップロードします。


ロード先として新規表を選択し、表名としてDEMO_FACILITY_LOCATIONSを入力します。主キーとしてID列を選択し、列のデータ型の使用チェックを入れます。

設定列見出し最初の行にヘッダーが含まれるチェックを入れ、ファイル・エンコーディングにはUnicode(UTF-8)を選択します。列デリミタとしてタブ囲み文字"は自動的に検出されます。

以上を設定して、データのロードをクリックします。


ロード結果を確認します。アプリケーションは別途作成するので、この画面からは作成しません。テーブルの表示を実行してロードされた内容を表示してみます。


のタブより、列の定義を確認します。緯度(LAT)経度(LON)NUMBER型で保存されていることは必須です。


データのタブより、列BUILDINGと列ADDRESSに、庁舎の名前住所が保存されていること、緯度(LAT)経度(LON)に数値が保存されていることを確認します。


データの準備は以上で完了です。

続いて空のアプリケーションを作成します。アプリケーション・ビルダーより作成を実行し、新規アプリケーションを選択してアプリケーション作成ウィザードを呼び出します。名前は任意ですが、今回の作業では役所の位置とし、それ以外は特に設定は行わずアプリケーションの作成を実行します。


以上で空のアプリケーションが作成されます。

このアプリケーションに、新しい地図の機能を使ったページを作成します。

ページ・アイテムによる地図の表示


タイプマップの表示であるページ・アイテムを使って、役所の位置を示す地図を表示させます。対話モード・レポートによって役所の一覧を表示し、役所を選択し開いたフォームに地図を含めます。

ページの作成を実行し、ページ作成ウィザードを起動します。


フォームを選択します。


フォーム付きレポートを選択します。


レポート・タイプ対話モード・レポートを選びます。レポート・ページ名役所一覧フォーム・ページ名役所情報とします。フォーム・ページ・モードモーダル・ダイアログを選びます。へ進みます。


ナビゲーションのプリファレンスとして、新規ナビゲーション・メニュー・エントリの作成を選択します。新規ナビゲーション・メニュー・エントリ役所一覧になります。へ進みます。


データ・ソースとしてローカル・データベースソース・タイプとしてSQL問合せを選択します。SQL SELECT文を入力には以下を記述します。
select
    jiscode
    ,name
    ,namekana
    ,building
    ,zipcode
    ,address
    ,tel
    ,source
    ,apex_spatial.point(lon,lat) map
from demo_facility_locations
APEXが提供しているAPIのAPEX_SPATIAL.POINTを使って、緯度経度の数値からSDO_GEOMETRY型を返すようにしています。

apex_spatial.point(lon,lat) map

GeoJSON(のポイント)の文字列をVARCHAR2型として返しても位置として認識されます。

'{"type":"Point","coordinates":[' || lon || ',' || lat || ']}' map

へ進みます。


フォームが認識する主キー列として、JISCODE (Number)を選択します。作成をクリックします。


以上で対話モード・レポートとフォームのページが作成されます。

ページ・デザイナで作成されたフォームのページを開き、マップを表示するページ・アイテムを作成します。列MAPはSDO_GEOMETRY型のためか、自動的にはフォームにページ・アイテムとして作成されません。

アイテムの作成を実行し、作成したページ・アイテムの識別名前P3_MAPとします。タイプマップの表示を選択します。ラベル地図とします。

設定に含まれているプロパティがマップ(地図)に関係します。背景ズーム・レベルマーカーの表示マーカーの色ツールチップマップ・コントールの表示対話型マップ高さを設定します。ツールチップに列のデータを含めるには、&P3_BUILDING.といった置換文字列を使うことができます。これらのプロパティの設定によって、マップの表示が変わります。今回の作業には含まれませんが、色々とプロパティを変更して地図の表示を確認してみてもよいでしょう。


ページ・アイテムのマップと、次に説明するミニマップは同じコンポーネントを使っています。そのため、ミニマップも同じ設定項目を持っています。

ページ・アイテムP3_MAPソースとして、フォーム・リージョン役所情報MAPデータ型SDO_GEOMETRYを指定します。問合せのみON主キーOFFセッション・ステートの保持リクエストごと(メモリーのみ)を選択します。


以上で地図を表示するページ・アイテムの作成ができました。動作を確認します。

役所一覧のページを表示します。


任意の役所の鉛筆アイコンをクリックし、フォームを表示します。ページ・アイテムP3_MAPとして保持している座標が、ページ・アイテムとして表示される地図の中心になります。


マップ・コントールや対話マップがONになっているため、拡大縮小、表示エリアの移動を行うコントロールが表示されています。またツールチップも表示されます。

ミニマップによる地図の表示


ページ・アイテムとして座標値を保持し、その座標値を中心とした地図を表示することができました。ただし、ページ・アイテムによるマップの表示は(動的アクションの)リフレッシュができません。そのため、ページ・アイテムに保持されている座標値を変更した後に、地図の表示を更新するにはページの送信を行う必要があります。

ミニマップを使い、ページの送信をせずに表示される地図を更新します。以下の動画のように動作するページを作成します。


ポップアップLOVに使用する共有コンポーネントLOVを作成します。共有コンポーネントLOVを開きます。


作成をクリックします。


LOVの作成として最初からを選びます。へ進みます。


名前LOV_FACILITIESとします。タイプにはDynamicを選択します。へ進みます。


データ・ソースローカル・データベースソース・タイプTableを選択します。表/ビューの名前としてDEMO_FACILITIY_LOCATIONS (表)を選択します。へ進みます。


戻り値としてJISCODE表示列としてBUILDINGを選択します。作成をクリックします。


LOVとしてLOV_FACILITIESが作成されます。作成されたLOVを編集するために開きます。


追加表示列を増やすために列の選択をクリックします。


今回はすべての列を表示列に含めます。更新をクリックします。最低限BUILDINGLATLONの列が必要です。


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


以上で共有コンポーネントのLOVが作成できました。

作成したLOVを使ったポップアップLOVを作成し、選択した役所をミニマップで表示するページを作成します。ページの作成を実行します。


空白ページを選択します。


名前ミニマップとします。ページ・モード標準です。オプションの静的コンテンツ・リージョンを開いて、リージョン1の名前を地図にします。へ進みます。


ナビゲーションのプリファレンスとして新規ナビゲーション・メニュー・エントリの作成を選択します。へ進みます。


確認の画面に遷移します。終了をクリックすると、静的コンテンツのリージョンをひとつだけ含むページが作成されます。


ページ・プロパティJavaScriptに含まれるファイルURLとして、ミニマップの実装となるJavaScriptのファイルを指定します。ファイル名は以下になります。

#APEX_FILES#libraries/apex/widget.miniMap.js


地図に表示する役所を選択するポップアップLOVとなるページ・アイテムを作成します。

アイテムの作成を実行します。識別名前P4_FACILITYタイプとしてポップアップLOVを選択します。ラベル役所とします。設定は特に調整しませんが、追加出力には以下を指定します。

LAT:P4_LAT,LON:P4_LON,BUILDING:P4_BUILDING

ポップアップLOVで役所を選択した際に、その緯度、経度、役所の名前をぞれぞれページ・アイテムP4_LATP4_LONP4_BUILDINGへ設定します。これらのページ・アイテムに設定された値をミニマップに渡します。

LOVのタイプに共有コンポーネントを選択し、LOVとして作成済みのLOV_FACILITIESを選択します。追加値の表示OFFNULL値の表示OFFとします。


追加出力を保存するページ・アイテムP4_LATP4_LONP4_BUILDINGを作成します。タイプ非表示設定保護された値OFFにします。以下のスクリーンショットはP4_LATですが、同様の設定でP4_LON、P4_BUILDINGを作成します。


作成済みの静的コンテンツのリージョン地図ソースHTMLコードとして、ミニマップを組み込みます。

<div id="myMap" style="width:100%;height:300px"></div>


リージョンのAttributesを開き、設定出力形式HTMLにします。


ポップアップLOVP4_FACILITYにて役所が選択されたときに、IDmyMapの要素に地図を書き出す動的アクションを作成します。P4_FACILITY動的アクションの作成を実行します。

動的アクションの識別名前役所の選択とします。ページ・アイテムを対象として動的アクションを作成しているため、タイミングはデフォルトでイベント変更選択タイプアイテムアイテムP4_FACILITYとなります。


TRUEアクションとしてJavaScriptコードの実行を選択します。設定コードとして以下を記述します。
$( "#myMap" ).miniMap( {
    center: [ $v("P4_LON"), $v("P4_LAT") ],
    background: "osm-bright",
    zoom: 14,
    marker: true,
    markerColor: "red",
    controls: true,
    interactive: true,
    tooltip: $v("P4_BUILDING")
 } );


以上で実装完了、といきたいところなのですが、若干の問題が残っています。

ポップアップLOVのP4_FACILITYで表示する役所を選択すると、LAT、LON、BUILDINGの値がページ・アイテムP4_LAT、P4_LON、P4_BUILDINGに設定されますが、この処理より先に動的アクションのJavaScriptコードが実行されてしまいます。結果としてひとつ前に選択されていた役所が地図に表示されます

ポップアップLOVによるページ・アイテムの設定が行われた後に、JavaScriptによるマップの表示が行われるよう、TRUEアクションを追加します。

TRUEアクションのJavaScriptコードの実行の前に、アクションを作成します。識別アクションとしてサーバー側のコードを実行を選択します。設定言語PL/SQLPL/SQLコードとして何もしないという意味のnull;を記述します。送信するアイテムとして、P4_LATP4_LONP4_BUILDINGを指定し、実行オプション結果を待機ONにします。


以上で作業は完了です。ページを実行すると、最初のGIF動画のような動作を確認できます。


カードにミニマップを組み込む


今回の記事の最初のGIF動画になっている、カード・リージョンのカードにそれぞれミニマップを表示させます。ファセット検索の対象として、カード・リージョンを使ったページを作成します。

ページの作成を実行し、ページ作成ウィザードを開始します。レポートを選択します。


ファセット検索を選択します。


ページ名役所検索とします。へ進みます。


ナビゲーションのプリファレンスとして新規ナビゲーション・メニュー・エントリの作成を選択します。へ進みます。


レポート・ソース表/ビューの名前としてDEMO_FACILITY_LOCATIONSを選択します。表示形式カードを選択します。適当な列がファセットとして認識されるよう、リフレッシュをクリックします。へ進みます。


表示形式としてグリッドを選択します。タイトル列BUILDINGとします。それ以外の列は指定せず、作成をクリックします。


ファセット検索のページが作成され、ページ・デザイナで開かれます。最初にページ・プロパティJavaScriptファイルURLにミニマップへのリンクを記述します。

#APEX_FILES#libraries/apex/widget.miniMap.js


カード・リージョン検索結果Attributesを開き、サブタイトルADDRESSとします。本体拡張フォーマットONにし、HTML式として以下を記述します。

<div class="card-map"
    data-center="[&LON.,&LAT.]"
    data-background="osm-bright"
    data-zoom="14"
    data-marker="true"
    data-marker-color="red"
    data-controls="true"
    data-interactive="true"
    data-tooltip="&BUILDING."
    style="width:100%;height:150px">
</div>


ミニマップの表示には上限がありページ区切りタイプスクロールの場合、ブラウザ側で以下のエラーが発生します。

Warning; Too many active WebGL contexts. Oldest context will be lost.

カード・リージョンのページ区切りタイプページにします。1ページ当たりのカードとします。


以上でカード・リージョンへのミニマップの組み込みは完了です。組み込まれたミニマップを描画する動的アクションを作成します。

リージョン検索結果動的アクションの作成を行います。

動的アクションの識別名前カードの変更とします。タイミングイベントとしてページ変更[カード]を選択します。選択タイプリージョンリージョンには検索結果を選択します。


TRUEアクション識別アクションとしてJavaScriptコードの実行を選択し、設定コードに以下を記述します。実行オプション初期化時に実行ONにします。

$(".card-map").miniMap();


以上でそれぞれのカードにマップが表示されるようになりました。ページを実行すると、本記事の先頭にあるGIF動画のような動作を確認できます。


ジオコーディングの使用


残念ながらジオコーディングは日本が対象に含まれていません。以下、簡単な使い方だけを紹介します。米国のRestonにあるオラクルのオフィスと以前に本社だったSan Mateoのオラクルのオフィスを表示させています。


ポップアップLOVを使ったミニマップのときと同じ手順で空白ページを作成します。ページ名前ジオコーディングとします。

市の情報を保持するページ・アイテムを作成します。識別名前P6_CITYタイプテキスト・フィールドとします。ラベルとします。ソースセッション・ステートの保持リクエストごと(メモリーのみ)を選択します。ページをリロードしたときにページ・アイテムの値を空白にリセットしたいので、このような設定にしています。


住所の情報を保持するページ・アイテムを作成します。識別名前P6_STREETタイプテキスト・フィールドとします。ラベル住所とします。ソースセッション・ステートの保持リクエストごと(メモリーのみ)を選択します。[Enter]を押すと送信ONにします。住所を入力してEnterを入力すると、ページ・アイテムP6_CITYとP6_STREETの値から緯度経度の座標値を求めるジオコーディングが行われます。


ジオコーディングされた座標を保持するページ・アイテムを作成します。識別名前P6_GEOCODINGとします。タイプとしてジオコーディングされた住所を選択します。ラベル地図とします。設定国タイプStaticとしてUnited Statesを選択します。この国の選択肢にJapanはありません。これはOracle Maps CloudのeLocation Serviceが対応していないことが理由になります。そのため、米国の住所でジオコーディングを行います。

通りアイテムとしてP6_STREET市アイテムとしてP6_CITYを選択します。ジオコーディングのトリガーAutomaticとします。セッション・ステートの保持リクエストごと(メモリーのみ)を選択します。


以上でジオコーディングを行うページが作成できました。

ページを実行し、としてReston住所Oracleを入力し、Enterを押してジオコーディングを行います。


ブラウザのJavaScriptコンソールを開き、ページ・アイテムP6_GEOCODINGの内容を印刷してみます。

$v("P6_GEOCODING");

JavaScriptコンソールにGeoJSON形式のポイント・データが印刷されます。

{"type": "Point", "coordinates": [-77.35322, 38.95358]}

同様に、としてSan Mateo住所Oracleを入力し、ジオコーディングを実行します。


P6_GEOCODINGの値は以下になります。

{"type": "Point", "coordinates": [-122.26387, 37.53239]}

ジオコーディングについては、動的アクションタイミングとしてジオコーディング・レスポンスおよび結果の選択があります。またアクションとしてはジオコーディングのトリガーが新規に追加されています。これらの使い方は、Oracle Spatialを使うワークショップで使用していたOracle Elocation Geocoderプラグインと同等だろうと思います。このワークショップを日本語で紹介した記事が参考になるでしょう。 

以上でOracle APEX 21.2の地図関連の拡張の紹介は終了です。

作成したアプリケーションのエクスポートを以下に置きました。
https://github.com/ujnak/apexapps/blob/master/exports/new212-map.sql

役所の位置データや表DEMO_FACILITY_LOCATIONSのDDLは含んでいません。データのみの二次配布が禁止されていることもありますが、ご自身で用途などをアマノ技研さんへ申告した上で利用されるのが良いかと思います。

少し長い記事になりましたが、Oracle APEXのアプリケーション作成の参考になれば幸いです。

追記

今回のminiMapの実装を含むJavaScriptファイルとして、以下を指定しています。

#APEX_FILES#libraries/apex/widget.miniMap.js

Oracle APEXは通常MinifyされたJavaScriptファイルも提供しています。その場合のパスは以下になります。

#APEX_FILES#libraries/apex/minified/widget.miniMap.min.js

今回のケースではMinifyされたJavaScriptファイルを指定する方が適切です。ただしMinifyされていないファイルには、ライブラリの使い方などが記載されています。例えばwidget.miniMap.jsの先頭には、以下の記載が含まれています。

 * miniMap is a basic implementaion of the underlying mapboxgl library to display maps pointing to given coordinates

 *

 * The markup expected by this widget is simply a empty div element. Optionally all provided options could also

 * be set by using data-* attributes.

 *

 * Supported options:

 * - background:        String which supplies name of the background map,

 *                      "default" uses "osm-bright" and for dark mode "osm-dark-matter"

 *                      Valid values: "default", "osm-bright", "osm-positron", "bi-world-map",

 *                                    "osm-dark-matter", "world-map"

 * - center:            An array containing the coordinates with latitude & longitude

 *                      [ longitude, latitude ], [ -122.26516, 37.52938 ]

 * - zoom:              [Defaults 16] The zoom level of the map, range between 0 and 18

 *                      additional values will add to the existing selection.

 * - marker:            [Defaults true] Whether to show a marker or not

 * - markerColor:       Color of the marker, if empty default color of mapbox is used

 * - controls:          [Defaults false] Whether to show controls or not

 * - interactive:       [Defaults true] Whether to have an interactive map or not

 * - tooltip:           If supplied, shows a tooltip popup above a marker, which is visible when the marker was clicked

 *                      The tooltip also supports HTML markup and APEX template directives

 *

 * File URL to reference: #IMAGE_PREFIX#libraries/apex/#MIN_DIRECTORY#widget.miniMap#MIN#.js