Oracle APEXで地理空間データを扱う

![3-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/7c8bf6bf-02c5-81bd-952f-f574f7f6f8aa.png)
# はじめに
Oracle Application Express（APEX）は、Oracle Databaseのためのローコード・アプリケーション・プラットフォームです。
先日、Oracle APEX上でOracle Databaseの地理空間機能、Oracle Spatialを扱うことができるようになりました。
本記事では、APEXとSpatialを組み合わせて、座標からの空間データを表示する対話型マップを含むアプリケーションを作成してみます。

# 前提条件
+ Oracle APEX バージョン21.1以上
※本記事のスクリーンショットは21.2です。

+ Autonomous Databaseインスタンスが構成済みであること
※APEX, Spatialはともに全てのOracle Databaseでもサポートされています。今回はADBを使用します。

+ 使用するサンプルデータのダウンロード
   + <a href="https://github.com/oracle/apex/" target="_blank" rel="noopener noreferrer">Oracle APEX on GitHub</a>にアクセス
   + **Branches**までスクロールダウンし、APEXのバージョンを選択
   + **sample-apps/sample-apps**と移動
   ※APEX 21.2であれば<a href="https://github.com/oracle/apex/tree/21.2/sample-apps/sample-maps" target="_blank" rel="noopener noreferrer">https://github.com/oracle/apex/tree/21.2/sample-apps/sample-maps</a>
   + airports-full.jsonとstates-full.jsonをダウンロード

# サンプルアプリのインストール
自分で一からアプリを作成する前に、まずはサンプルのアプリから始めてみます。
Oracle APEXには、いくつかの機能を紹介するサンプル・アプリケーションのポートフォリオがあります。今回はOracle APEXの地理空間機能を紹介するSample Mapsアプリケーションを使用します。

## アプリケーションのインストール
1. ワークスペースのホーム画面から**アプリケーション・ビルダー**をクリックします。
![1-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/99a18062-4fab-f377-3b00-2ebde08194f7.png)

1. **スタータまたはサンプル・アプリケーションのインストール**をクリックします。
![1-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/4dc6d20e-ab14-0ff9-1eac-5fad509873f6.png)

1. **サンプル**をクリックすると、サンプルアプリのリストが表示されます。
![1-3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/4f5bd192-cbb8-3e24-f8f2-68631ac50c58.png)

1. スクロールダウンし、**Sample Maps**の**Download App**をクリックし、**sample-maps.zip**をダウンロードします。
![1-4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/b56bc51a-c357-30ae-2c03-a8736b48dddc.png)

1. アプリケーション・ビルダーに戻り、**インポート**をクリックします。
![1-5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/97c7a911-5877-3ef6-ba8e-0a6b3610be99.png)

1. sample-maps.zipをドラッグ&ドロップし、次をクリックします。
![1-6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/3636caf1-786f-3cd1-6c14-00ce87ca6f10.png)

1. デフォルトの設定のまま、**アプリケーションのインストール**をクリックします。
![1-7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/adb3022d-36d7-5ce9-bc94-3492b4edad70.png)

1. 全てデフォルト設定で、**インストール**をクリックします。
![1-8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/ed5bdf26-0500-ec1d-21ef-5b4ed2ed61b2.png)

1. アプリケーションを実行します。
![1-9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/6e1967f7-0700-9315-49cd-175fca68cbbd.png)

1. APEXのワークスペースのユーザー名とパスワードでサインインします。
![1-10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/79432c6a-4c5d-928c-23a4-b242e54f88d1.png)

## データのロード
1. 初回起動時にデータのロードに関するメッセージが表示されます。**Data Loading**をクリックします。
![2-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/61c4add1-98a7-45aa-cbe7-f38ac7e67aba.png)

1. **Upload Files**を選択し、US States Dataにstates-full.json, Airports Dataにairports-full.jsonをアップロードし、データセットをロード
![2-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/32772f74-ce88-1215-702d-cc990230090e.png)

## サンプルアプリの確認
1. タイルをクリックすると、機能の関連ページに移動します。今回は**Map and Report**をクリックします。
![3-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/2fb01e63-3ff6-f1a1-49a3-2ec4a3193571.png)

1. 地図上に空港の位置情報がマッピングされ、ピンアイコンをクリックすると詳細情報が表示されます。
![3-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/bd41499e-984a-e28e-b1ff-9c35cb7eddd9.png)

1. 他にもヒートマップなど様々なデータ探索の仕方ができるページがあります。
![3-3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/862a6448-e93b-1618-e617-fb4d4c8f4d05.png)

## サンプルデータの確認
1. APEX画面に戻り**SQLワークショップ**をクリックし、**オブジェクト・ブラウザ**に移動します。
![4-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/108131e3-0e15-c3bc-c8de-450b63d4f642.png)

1. **EBA_SAMPLE_MAP_AIRPORTS**のデータを確認します。右側にスクロールしてジオメトリの列を表示すると、空港は点として保存されるため、点のジオメトリ値が入っています。
![4-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/a2cf4d67-7bba-bf29-8145-5423c7dd1f7c.png)

1. **EBA_SAMPLE_MAP_SIMPLE_STATES**のデータを確認します。ジオメトリの列を表示すると、州はポリゴンとして保存されます。
![4-3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/ac8f6ed1-74ea-9ab4-5834-b84199b0552f.png)

1. MDRT_...$というテーブルは、他のテーブルの空間索引をサポートするために自動で作成されます。これらは無視してかまいません。
![4-4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/6006c9c4-16d0-807c-46df-8c3d6f3df408.png)

# ゼロから地図アプリを作る
次はゼロから地図上に空港の位置をマッピングするアプリを作成してみます。
## ページの作成
1. アプリケーション・ビルダーから新たにアプリを作成します。
![5-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/a03a432a-7c14-5501-1175-f0b3a182d6d5.png)

1. **新規アプリケーション**をクリックします。
![5-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/0627b3f4-d9e6-81d6-5fbc-c007886a89cf.png)

1. 名前はAPEX Mappingとします。**ページの追加**をクリックします。
![5-3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/da895081-f7ec-bd26-a859-214be3361681.png)

1. ゼロから作るので、空白ページを選択します。
![5-4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/8949403a-adff-cb7f-b1ea-df77f7e1efc1.png)

1. ページの名前はAirports and States Mapとします。
![5-5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/ce60900b-5e17-62e1-8eed-5b455c8cd5e3.png)

1. こちらで**アプリケーションの作成**をクリックします。
![5-6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/75a4f3d8-f1d6-b25d-f77c-57861887041c.png)

## 空港マップをページに追加
1. 作成したAirports and States Map の編集画面にいきます。リージョンパレットからマップをドラッグし、Bodyセクションの下にドロップします。
![6-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/b308d68c-73be-ab18-01e0-ab07a0a911b1.png)

1. リージョンのタイトルを新規から**My Map Region**へ変更します。
![6-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/23b1dd41-7c7a-0bf7-a4e9-b6b6783f5f94.png)

1. マップリージョンにはレイヤーという子要素が含まれています。レイヤーは、地図上にレンダリングされるデータコンテンツです。名前を**空港**に変更し、レイヤー・タイプがポイントになっていることを確認します。
![6-3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/72adad4c-ab24-0c40-351f-fb7ecafefda1.png)

1. ソースとして表名に**EBA_SAMPLE_MAP_AIRPORTS表**を指定します。レンダリングされる空港を制限するため、where句に**LAND_AREA_COVERED > 2500**を追記します。また、**空間索引の使用**ボタンをONにします。
![6-4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/ac9817bb-fdbb-9124-d14d-175f38bacd37.png)

1. 下にスクロールして、[列のマッピング]セクションに移動します。ジオメトリ型のデータ型SDO_GEOMETRYとジオメトリ列GEOMETRYを選択します。また、主キー列にIDを指定します。
![6-5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/b2c2e81f-daac-b8da-160d-5af26423c73e.png)

1. 下にスクロールして、[情報ウィンドウ]セクションに移動します。ここでは、マップ内のアイテムをクリックしたときにポップアップで表示する情報を設定できます。拡張フォーマットボタンをONにし、HTML式に以下をペーストします。
    ```html
    <strong>&AIRPORT_NAME.</strong><br>
    &CITY., &STATE_NAME.<br>
    Code: &IATA_CODE.
    ```
![6-6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/4bef4fac-e6de-34dc-ec68-2aa01d76b1c5.png)

## 州マップの追加
1. 「レイヤー」を右クリックし、「レイヤーの作成」を選択します。
![7-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/327fd960-9a37-217f-48fb-6a694fbc9195.png)

1. 名前を州、レイヤー・タイプをポリゴン、ソースを**EBA_SAMPLE_MAP_SIMPLE_STATES**に設定します。
![7-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/70a40ca2-93b9-0fab-2ac4-023f1a12417d.png)

1. レイヤーはページツリーに表示されている順番でレンダリングされます。空港を州の上にレンダリングするため、州レイヤーを空港レイヤーの上にドラッグします。また、[列のマッピング]でジオメトリ列GEOMETRYを選択します。[外観]で塗りつぶしの色と不透明度を設定します。ただし、不透明度を1にしてしまうと、背景の地図が見えなくなってしまうので注意してください。そしてツールチップにNAME列を指定します。
![7-3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/2c7a3efc-7ad6-8f05-d3b9-07428cacb7cf.png)

1. こちらで保存し、実行します。
![7-4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/d74f4f43-644b-c6c6-f23f-b381c6a37499.png)

1. 空港をクリックすると設定した情報ウィンドウが表示されます。また、州の上にカーソルを置くと、設定したツールチップ、州の名前が表示されます。
![7-5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/e24d6570-2e65-5d9f-efb8-1ec862f3e851.png)

# 空間分析
続いて上で作成したマップを拡張します。選択した州の中から、ユーザーが決めた距離の範囲内にある空港を検索できるようにします。
## フィルタリングのためのリージョンを追加
1. 左側のツリー一番上の**ページ2:Airports and States Map**をクリックします。次に、右側のプロパティパネルで、[外観]のページ・テンプレートを**Left Side Column**に変更します。
![8-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/f1f07fc7-8fc0-76a9-eef7-dcdaa59e6260.png)

1. 静的コンテンツをLEFT COLUMNにドラッグします。
![8-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/b67c986d-bad5-f635-e8e4-eaee2b121787.png)

1. 名前を**My Filters Region**に変更します。
![8-3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/7ad1bae0-b138-41cb-1b1b-f0df750de437.png)

## 州の選択リストの作成
1. アイテム：選択リストをREGION BODYにドラッグし、名前をP2_STATE、ラベルを州にします。
![9-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/12bac5d0-6e6c-1b4d-bc98-3ce6a6cee9d0.png)

1. 右側のアイテムプロパティで、[検証]で必須の値に設定します。そしてLOVのタイプを**SQL問合せ**にし、以下のクエリを入力します。また、追加値の表示・NULL値の表示はOFFにします。
    ```sql
    select name, state_code
    from EBA_SAMPLE_MAP_SIMPLE_STATES
    order by name
    ```
    ![9-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/970286e6-82fe-56e1-213f-c40ef4e77cd5.png)

1. スクロールダウンし、[デフォルト]セクションでタイプを**静的**に、静的値を'Texas'または任意の州（'で囲む）に設定します。
![9-3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/3578c5c5-b73a-f546-b01b-7026ce780de4.png)

## 距離の入力項目を追加
1. アイテム：数値フィールドをREGION BODYにドラッグし、名前をP2_DISTANCEにします。またラベルを距離(km)とします。
![10-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/f1f65b2f-dcd0-e1fe-5d92-2443e8f7f18d.png)

1. スクロールダウンし、[検証]セクションで必須の値をONにします。
![10-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/c32424c7-1414-5fc2-c36d-33a99f2e211f.png)

1. スクロールダウンし、[デフォルト]セクションでタイプ：静的、静的値：100と入力します。
![10-3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/e8db09ab-6878-62d1-88dc-91ebd0298d26.png)

## 動的アクションの作成
1. P2_DISTANCEを右クリックし、**動的アクションの作成**をクリックします。
![11-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/9d0942e2-84d3-874b-8b4a-ce114f6092cd.png)

1. 動的アクションのプロパティで、名前を**検証と更新**に設定します。[タイミング]セクションでイベント：**変更**、選択タイプ：**アイテム**、アイテム：**P2_DISTANCE, P2_STATE** に設定します。続いて距離に負の値を送信させないように、[クライアント側の条件]セクションでタイプ：**アイテム => 値**、アイテム：**P2_DISTANCE**、値：**0**に設定します。
![11-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/27127c84-dc2e-a2d1-aa5d-9bfe3aec36fc.png)

1. 動的アクションは、設定された条件に基づいて呼び出されるTRUEアクションとFALSEアクションで構成されています。クライアント側の条件（P2_DISTANCE >= 0)がTRUEである場合、入力値を送信し、ページを更新する必要があります。アクションプロパティで、アクション：**リフレッシュ**、選択タイプ：**リージョン**、リージョン：**My Map Region**と設定します。
![11-3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/ff09ce9d-11f3-8cd1-e7b6-d07a9a1c4ad7.png)

1. 次に、クライアント側の条件が満たされない場合、つまり負の距離値が入力された場合に呼び出されるアクションを設定します。[False]を右クリックし、**FALSEアクションの作成**をクリックします。
![11-4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/9ae1d06f-8205-33ec-19c2-4e685e8cb25f.png)

1. FALSEアクションは、ユーザーへのポップアップメッセージとします。アクション：**アラート**に設定し、タイトルとメッセージを入力します。
![11-5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/26a12cba-c4e2-a543-c6e3-6d314974f87a.png)

1. My Map Region には現在すべての州が表示されるようになっています。ここから、P2_STATEで選択した州のみ表示させるようにします。州レイヤーを選択し、レイヤープロパティの[ソース]セクションのWHERE句で**state_code = :P2_STATE**と入力します。
![11-6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/d923d3d2-f803-3253-f4fc-718d2ffd62e0.png)

1. 最後に、ユーザーが入力した州と距離の範囲内の空港が表示されるよう空港レイヤーを修正します。空港レイヤーを更新し、レイヤープロパティの[ソース]セクションのタイプを**SQL問合せ**にし、以下のクエリを入力します。
    ```sql
    select a.*
    from EBA_SAMPLE_MAP_AIRPORTS a, 
         EBA_SAMPLE_MAP_SIMPLE_STATES b
    where b.state_code= :P2_STATE
    and a.land_area_covered > 1000
    and sdo_within_distance(a.geometry, b.geometry, 'distance='|| :P2_DISTANCE ||' unit=KM') = 'TRUE'
    ```
    送信するページ・アイテムには、**P2_STATE, P2_DISTANCE**を設定します。
    ![11-7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/c411c6c4-5d63-61b8-0664-d2a2d515f7f5.png)

1. アプリケーションを保存・実行します。州でAlabamaを選択すると、アラバマ州と100km以内の空港が地図上に表示されています。
![11-8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/8522490c-a116-9f09-0042-eaff6a7c6883.png)

1. 次に州でKansasを選択し、距離で600を指定すると、以下のように表示されます。
![11-9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/4da1c41c-5b04-6ec5-66df-f1d477a2f569.png)

1. 最後に距離に負の値を入力してみると、正しく設定したポップアップが表示されました。
![11-10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/0c53a13d-7d27-c9a4-5674-8a90e85ba80d.png)

# おわりに
サンプルアプリで示したように、APEX上でOracle Spatial機能を使ってできることは他にもたくさんあります。本記事では基本的な機能を持つアプリを作成しましたが、上手く活用すればより高度な空間分析も可能ですので、ぜひお試しいただければと思います。

# 参考資料
+ <a href="https://qiita.com/ryotayamanaka/items/6f9954e63dac03fd44d0" target="_blank" rel="noopener noreferrer">Oracle Spatial を使った接触追跡</a>
+ <a href="https://qiita.com/wahagon/items/d8007bef2934536cf0b3" target="_blank" rel="noopener noreferrer">Oracle Spatial Studio で駅データを表示してみた</a>
+ <a href="https://qiita.com/wahagon/items/3acf445ce9e9ee4a5cbc" target="_blank" rel="noopener noreferrer">Oracle Spatial Studio で駅ごとの年齢層の人口を集計してみた</a>
+ <a href="https://apexapps.oracle.com/pls/apex/dbpm/r/livelabs/view-workshop?wid=936" target="_blank" rel="noopener noreferrer">Get Started with Maps and Spatial in APEX Workshop</a>