2025年9月25日木曜日

USGSの地震情報からOracle Spatialを使ってVector Tileを生成し地図に表示する

USGS(U.S. Geological Survey - アメリカ地質調査所)のサイトからダウンロードできる地震の発生履歴をOracle Databaseにロードします。ロードした地震の震源をOracle Spatialの機能を使ってVector Tileを生成し、MapLibreとOpenLayersのレイヤーとして重ね合わせます。

すべてをOracle APEXのアプリケーションに実装します。Oracle APEXには標準でマップ・リージョンがあります。APEXのマップ・リージョンではMapLibreが使われていて、データベースに保存されているデータからレイヤーを生成するために、内部ではOracle SpatialのVector Tileを活用しています。最初にAPEX標準のマップ・リージョンに地震の震源を表示し、その後にMapLibreとOpenLayersにVector Tileを表示する実装を行います。

MapLibreへの実装については、Oracle SpatialのProduct ManagerであるKarin PatengeさんのMediumの記事「How to create vector tiles from spatial data managed in the Oracle Database? 」に多くを拠っています。

作成するAPEXアプリケーションは以下のように動作します。


最初にUSGSのサイトを開き、地震のデータをCSV形式でダウンロードします。

https://earthquake.usgs.gov/earthquakes/search/

Output Optionsの指定はCSVです。それ以外は興味のある条件を指定します。条件を設定してSearchボタンをクリックすると、query.csvというファイル名で条件に合った地震のデータがダウンロードされます。


APEXアプリケーションの作成に移ります。作業は以下の順番で実施します。
  1. データ・ロード定義と手動によるデータ・ロード画面を作成します。
  2. APEXのマップ・リージョンによる震源の表示画面を作成します。
  3. Vector Tileを返すORDS RESTサービスを作成します。
  4. APEXのページにMapLibreを組み込みVector Tileを表示します。
  5. APEXのページにOpenLayersを組み込みVector Tileを表示します。
地震の震源を保持する表USGS_EARTHQUAKESを作成します。ChatGPTにquery.csvを渡して生成させたDDLを若干手直しています。表のDDLの生成はAPEXのデータ・ワークショップでもできますが、LLMを使用する方が精度が高いと感じます。

SQLワークショップSQLコマンドで実行します。


空のAPEXアプリケーションを作成します。名前USGS Earthquakesとします。


空のアプリケーションが作成されます。共有コンポーネントデータ・ロード定義の作成から始めます。


データ・ロード定義を開きます。


作成をクリックします。


データ・ロードの作成として最初からを選択します。

へ進みます。


作成するデータ・ロード定義の名前USGS Earthquakesとします。ターゲット・タイプ表名に先ほど作成したUSGS_EARTHQUAKESを選択します。

へ進みます。


サンプル・データとしてUSGSのサイトからダウンロードしたquery.csvアップロードします。

へ進みます。


列のマッピングマップ先を設定します。CSVの最初の行にヘッダーとして記述されている列名と表の列名が完全に一致している場合は、自動的にマップ先が決まります。今回はCSVの列名と表の列名が微妙に異なっているため、未設定のマップ先を手動で設定する必要があります。

EVENT_IDについては、主キーチェックします。

ファイル・エンコーディングUnicode(UTF-8)を選択します。

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


データ・ロード定義としてUSGS Earthquakesが作成されました。表USGS_EARTHQUAKESには列EPICENTERとして、震源の位置を保持するSDO_GEOMETRY型の列があります。CSVに含まれる経度、緯度の情報からSDO_GEOMETRYの値を生成するように、データ・プロファイル列を追加します。

データ・ロード定義USGS Earthquakesを編集します。


データ・プロファイルの編集を開きます。


列の追加をクリックします。


列タイプSQL式を選択します。名前EPICENTERです。これは表USGS_EARTHQUAKESにある列EPICENTERに対応します。データ型ジオメトリ(SDO_GEOMETRY)を選択します。

ソースSQL式に以下を記述します。APEX_SPATIAL.POINTはSRIDを4326とした座標を生成する簡易ファンクションです。

apex_spatial.point(LONGITUDE,LATITUDE)

以上の設定で、データ・プロファイル列EPICENTER作成します。


データ・プロファイル列としてEPICENTERが作成されます。変更の適用をクリックします。


作成されたデータ・ロード定義の静的IDusgs_earthquakesとなっていること、およびデータ・ロード定義の主キーとして列EVENT_IDが設定されているため、ロード・メソッドマージが選択されていることを確認します。

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


以上でデータ・ロード定義USGS Earthquakesが作成されました。


作成したデータ・ロード定義を元に、手動でデータをロードするページを作成します。

ページの作成を開始します。


データのロードを選択します。


作成するデータ・ロードのページのページ番号名前Loadとします。ページ・モード標準です。

データ・ロード属性データ・ロードに先ほど作成したデータ・ロード定義USGS Earthquakesを選択します。データのアップロード元としてファイル最大ファイル・サイズ(MB)50を設定します。

以上の設定を行い、ページの作成をクリックします。


データ・ロードのページが作成されます。作成されたページを実行し、query.csvを表USGS_EARTHQUAKESにロードします。


ファイルの選択をクリックし、query.csvを選択します。


query.csvの内容がプレビューされます。データのロードをクリックします。


データのロードが完了します。


表USGS_EARTHQUAKESに震源のデータが投入されました。震源の座標は列EPICENTERにSDO_GEOMETRYとして保存されています。この列に空間索引を作成します。

SQLコマンドを開き、列EPICENTERにデータが投入されていることを確認します。

select count(*) from usgs_earthquakes where epicenter is null;

データ・プロファイル列が正しく設定されていれば、結果は0行になります。


空間索引USGS_EARTHQUAKES_SIDXを作成します。
CREATE INDEX usgs_earthquakes_sidx
ON usgs_earthquakes (epicenter)
INDEXTYPE IS MDSYS.SPATIAL_INDEX_V2 PARAMETERS ('LAYER_GTYPE=POINT');

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

最初にAPEXのマップ・リージョンを使って地震の震源を表示します。

ページの作成をクリックします。


マップを選択します。


ページ番号名前Earthquakesとします。ページ・モード標準です。

データ・ソースソース・タイプを選択し、表/ビューの名前USGS_EARTHQUAKESを選択します。

へ進みます。


マップ・スタイルポイントを選択します。マップ属性ジオメトリ列タイプジオメトリ列とし、ジオメトリ列としてEPICENTER(Sdo_Geometry)を選択します。

ツールチップ列PLACE(Varchar2)を選択します。ページ作成後に詳細な情報を表示するように、この設定は変更します。

ファセット検索ページの作成オンにし、発生日時やマグニチュードによる範囲検索を実装できるようにします。

以上の設定を行い、ページの作成をクリックします。


マップのページが作成されます。

ファセットの同期を実行します。ソースである表USGS_EARTHQUAKESの列がファセットとして作成されます。この中から、発生時刻P3_TIME_UTCとマグニチュードP3_MAGを残し、他はコメント・アウトします。


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

識別タイプ範囲に変更し、ラベルTimeとします。ソースデータ型はなぜかVARCHAR2になっているため、TIMESTAMP WITH TIME ZONEに変更します。


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

識別タイプ範囲に変更し、ラベルMagnitudeとします。こちらもソースデータ型はなぜかVARCHAR2になっているため、NUMBERに変更します。


震源の表示形式を設定します。

マップのレイヤーEarthquakesを選択します。

今回は使用しませんが表USGS_EARTHQUAKESには主キー列があるため、列のマッピング主キー列EVENT_IDを設定します。

ポイント・オブジェクトスタイルアイコンアイコン・ソースアイコン・クラスアイコンCSSクラスとしてfa-dot-circle-oを設定します。

外観塗りつぶしの色として#8b3626塗りつぶしの不透明度0.5を設定します。これはポイント・オブジェクトの色と透明度になります。

ツールチップ拡張フォーマットオンにし、HTML式として以下を記述します。
Date: &TIME_UTC.<br>
Mag: &MAG.<br>
Place: &PLACE.
レイヤーは1つだけなので、凡例表示オフにします。


マップの初期位置を日本にします。

マップ属性を開き、初期位置およびズームタイプ静的値経度139.6917緯度35.6895ズーム・レベルを設定します。


以上でAPEXのマップ・リージョンのページは完成です。

ページを実行し、ファセット検索などを行いマップの表示を確認します。


次に表USGS_EARTHQUAKESの列EPICENTERよりVector Tileを生成する、ORDSのRESTサービスを作成します。

ORDSのRESTサービスは、Karin Patengeさんの元記事に概ね準じて作成します。出力するVector Tileを決める変数x, y, zについてはMapLibreが指定します。それに追加してマグニチュードの下限を指定できるように、バインド変数mを追加しています。

Vector Tileを返すGETハンドラのパターンに、バインド変数mを追加しています。

vt/:m/:z/:x/:y

GETハンドラのソースは、SIMPLE_PREDICATEにマグニチュードの下限を設定したSELECT文に置き換えています。
SELECT 
    'application/vnd.mapbox-vector-tile' as mediatype, 
    SDO_UTIL.GET_VECTORTILE( 
        TABLE_NAME => 'USGS_EARTHQUAKES', 
        GEOM_COL_NAME => 'EPICENTER', 
        ATT_COL_NAMES => sdo_string_array('PLACE','MAG','TIME_UTC','DEPTH_KM','LATITUDE','LONGITUDE'), 
        SIMPLE_PREDICATE => sdo_string_array('MAG','>=',to_char(nvl(:m,0))), 
        TILE_X => :x, 
        TILE_Y_PBF => :y, 
        TILE_ZOOM => :z 
    ) AS vtile 
FROM dual
また、RESTサービスをAPEXのセッションで保護しています。

以下のスクリプトを実行することにより、Vector Tileを返すORDS RESTサービスが作成されます。C_URL_MAPPING_PATTERNには、ORDS別名またはAPEXのワークスペース名を設定します。

作成したORDS RESTサービスは以下のパスで呼び出されます。

http[s]://ホスト名:ポート番号/ords/[ORDS別名]/usgs/vt/:m/:z/:x/:y

APEXセッションによる認証を許可するため、APEXのグループとしてRESTful Servicesを作成し、APEXのユーザーを所属させます。

管理メニューのユーザーとグループの管理を開きます。


グループ・タブを開きます。グループRESTful Servicesが作成されていない場合は、ユーザー・グループの作成を実行します。


グループ名をRESTful Servicesとして、グループの作成をクリックします。


以上でグループRESTful Servicesが作成されます。

APEXユーザーにグループRESTful Servicesを割り当てます。

ユーザーの編集画面を開き、グループ割当てよりRESTful Servicesを割り当てます。

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


グループRESTful Servicesを割り当てられたユーザー(が開始したAPEXセッション)から、先ほど作成したORDSのRESTful Servicesが呼び出せるようになりました。

素のMapLibreをAPEXのページに組み込み、ORDSのRESTサービスを呼び出して取得したVector Tileをレイヤーとして重ね合わせます。

ページの作成を呼び出し、空白ページを作成します。

ページ番号名前Vector Tilesとします。ページの作成をクリックします。


表示するマグニチュードの下限を設定するページ・アイテムをP4_MAGとして作成します。

タイプ数値フィールドラベルMagnitudeとします。検証必須の値オンデフォルトタイプ静的を選択し、静的値としてを設定します。

セッション・ステートストレージセッションごと(永続)を設定します。


MapLibreによるマップを表示する静的コンテンツのリージョンを作成します。

ソースHTMLコードに以下を記述します。

<div id="map"></div>

リージョンの高さを固定する必要があるため、外観テンプレート・オプションBody Height640pxを設定します。また、Remove Body Paddingをチェックします。


ページ・プロパティにMapLibreを初期化する設定を行います。

JavaScriptファイルURLに以下を設定します。

https://cdn.jsdelivr.net/npm/maplibre-gl@5.7.3/dist/maplibre-gl.min.js

ページ・ロード時に実行に以下を記述します。ORDS RESTサービスの呼び出しをAPEXセッションで認証するために、カスタム・ヘッダーとしてApex-Sessionを送信するようにしています。

CSSファイルURLに以下を設定します。

https://cdn.jsdelivr.net/npm/maplibre-gl@5.7.3/dist/maplibre-gl.min.css

インラインに以下を記述します。

#map { position: absolute; top: 0; bottom: 0; width: 100%; }


以上で、MapLibreのマップにVector Tileを重ね合わせたページは完成です。


マップ・ライブラリにMapLibreの代わりに、OpenLayersを使ったページを作成します。

ページの作成を呼び出し、空白ページを作成します。

ページ番号名前OpenLayersとします。ページの作成をクリックします。


表示するマグニチュードの下限を設定するページ・アイテムをP5_MAGとして作成します。MapLibreのページで設定したページ・アイテムP4_MAGと同じ設定です。


OpenLayersによるマップを表示する静的コンテンツのリージョンを作成します。これもMapLibreのページで作成したリージョンと同じ設定です。


ページ・プロパティにOpenLayersを初期化するための設定を行います。

JavaScriptファイルURLに以下を設定します。

https://cdn.jsdelivr.net/npm/ol@10.6.1/dist/ol.js

ページ・ロード時に実行に以下を記述します。OpenLayersのコードは、ほとんどClaude Sonnet 4に書いてもらっています。

CSSファイルURLに以下を設定します。

https://cdn.jsdelivr.net/npm/ol@10.6.1/ol.min.css

インラインに以下を記述します。

#map { position: absolute; top: 0; bottom: 0; width: 100%; }


以上で、OpenLayersのマップにVector Tileを重ね合わせたページは完成です。


最後にUSGSのサイトから地震のデータをダウンロードし表USGS_EARTHQUAKESの内容を更新する機能を、ホーム・ページに実装します。

ダウンロードするデータを絞り込む条件に使用するページ・アイテムを、P1_START_TIMEP1_END_TIMEP1_MAGとして作成します。

ページ・アイテムP1_START_TIMEタイプ日付ピッカー設定時間の表示オンにして、書式マスクYYYY-MM-DD HH24:MI:SSを設定します。


P1_END_TIMEについても同様に設定します。P1_END_TIMEはP1_START_TIMEの右隣に配置するため、レイアウト新規行の開始オフにします。


ページ・アイテムP1_MAGのタイプは数値フィールド列スパンを設定します。


表USGS_EARTHQUAKESにロードした行数を表示するページ・アイテムとして、P1_PROCESSED_ROWSを作成します。タイプ表示のみです。

設定改行の表示ページの送信時に送信ともにオフにし、ページ・アイテムを横並びにするためレイアウト新規行の開始オフにします。列スパンにします。


データをロードするボタンとしてLOAD、データを削除するボタンとしてCLEARを作成します。双方とも動作アクションはデフォルトのページの送信です。


ボタンCLEARについては動作確認の要求オンにし、確認メッセージスタイルを設定します。


表USGS_EARTHQUAKESの内容を確認するため、対話モード・レポートのリージョンを作成します。ソース表名としてUSGS_EARTHQUAKESを設定します。


ボタンLOADをクリックしたときに実行するプロセスを作成します。

識別名前Update Earthquakes events from USGSとします。ソースPL/SQLコードに以下を記述します。

サーバー側の条件ボタン押下時LOADを指定します。


ボタンCLEARをクリックしたときに実行するプロセスを作成します。

識別名前Delete all rows in USGS_EARTHQUAKESとします。ソースPL/SQLコードに以下の1行を記述します。

delete from usgs_earthquakes;

サーバー側の条件ボタン押下時CLEARを指定します。


以上で、地震のデータをUSGSのサイトより直接ロードする画面ができました。


以上でアプリケーションはすべて完成です。

今回の記事で作成したAPEXアプリケーションのエクスポートを以下に置きました。
https://github.com/ujnak/apexapps/blob/master/exports/usgs-earthquakes-with-vector-tiles.zip

APEXにはマップ・リージョンがあるため、このような方法でOracle SpatialのVector Tilesを活用することは稀だとは思います。そのためMapLibreやOpenLayersでの実装では、APEXの機能は極力使用せずJavaScriptのコードをページ・プロパティに記述しています。また、レイヤーとしてVector Tilesを組み込むコードについては、AIがほとんどのコードを生成してくれました。

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

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