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が作成されます。変更の適用をクリックします。


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

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


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


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

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


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


作成するデータ・ロードのページのページ番号は2、名前は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のマップ・リージョンを使って地震の震源を表示します。

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


マップを選択します。


ページ番号は3、名前は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、ズーム・レベルに4を設定します。


以上で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をレイヤーとして重ね合わせます。

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

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


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

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

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


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

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

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

リージョンの高さを固定する必要があるため、外観のテンプレート・オプションのBody Heightに640pxを設定します。また、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を使ったページを作成します。

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

ページ番号は5、名前は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_TIME、P1_END_TIME、P1_MAGとして作成します。

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


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


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


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

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


データをロードするボタンとして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のアプリケーション作成の参考になれば幸いです。

完