2025年6月12日木曜日

GeoSPARQLで問い合わせたデータをAPEXのマップに表示する

Oracle DatabaseはRDF Graphをサポートしています。また、GeoSPARQLもサポートしています。これらの機能については、Oracle Database 23aiの以下のドキュメントで説明されています。

Graph Developer's Guide for RDF Graph

先日、以下の記事にてSPARQLクエリをOracle REST Data Services APIより呼び出すアプリケーションについて紹介しています。

Oracle REST Data ServicesのRDF Graph APIを呼び出してSPARQLを実行する

今回の記事では、Matt Perryさんの以下の記事を参考にOracle DatabaseでGeoSPARQLの問合せを実行できる環境を作成し、APEXのアプケーションから実行してみます。

Exploring GeoSPARQL 1.1 Features in Oracle Database 23ai
https://medium.com/oracledevs/exploring-geosparql-1-1-features-in-oracle-database-23ai-c58085190b07

Matt Perryさんの記事ではAutonomous DatabaseにRDF Graphを作成して、主にDatabase ActionsのSQLワークショップからGeoSPARQLの問い合わせを行っています。本記事ではAutonomous DatabaseにRDF Graphを作成するところは同じですが、ネットワークやモデルの作成、データのインポートおよびSPARQLクエリの実行を、Oracle REST Data Services APIを呼び出して実行します。

作成したAPEXアプリケーションは以下のように動作します。自然言語の問合せより生成AI(OpenAI GPT-4.1)を呼び出し、GeoSPARQLのクエリを生成しています。その後、生成されたクエリを実行しています。

クエリの実行結果をAPEXアプリケーションのマップ上に表示しています。


上記のAPEXアプリケーションのエクスポートを以下に置きました。
https://github.com/ujnak/apexapps/blob/master/exports/sparql-operations-for-geo.zip

以前の記事「Oracle REST Data ServicesのRDF Graph APIを呼び出してSPARQLを実行する」で作成したアプリケーションに、以下の2つのページを追加しています。
  • Upload - RDF GraphにN-Triplesをアップロードする画面
  • Map - 生成AIでGeoSPARQLクエリを生成し、そのクエリの検索結果をマップに表示するページ(GIF動画のページ)

以下より、APEXアプリケーションに追加した機能とAPEXアプリケーションを使用するにあたって必要な作業を紹介します。

APEXアプリケーションをインポートすると作成されるリモート・サーバーおよびWeb資格証明を更新します。

ワークスペース・ユーティリティリモート・サーバーを開くと、RDF Graph ORDS Endpointが作成されています。

これを開きます。


一般エンドポイントURLを、ORDS Data Services APIのRDF Graphを呼び出すエンドポイントに変更します。エンドポイントURLは一般に以下の形式になります。

https://[ホスト名]/ords/[ワークスペース名]/_/db-api/stable


ワークスペース・ユーティリティWeb資格証明を開きます。


最初にRDF Graph Credを開きます。

クライアントIDまたはユーザー名に、RDF Graphを作成するスキーマ名を設定します。クライアント・シークレットまたはパスワードは、そのスキーマのパスワードを設定します。


次にopenai api keyの資格証明を開きます。

資格証明シークレットにAuthorizationヘッダーに設定する値、つまりBearerに空白で区切ってOpenAIのAPIキーを繋げた値を設定します。以下の形式になります。

Bearer [OpenAIのAPIキー]


以上でAPEXアプリケーションを使用するに当たって必要な設定ができました。

ホーム・ページを開いて、RDFネットワークを作成します。

Network NameRDF_NETWORKNetwork OwnerはRDFネットワークを保存するスキーマの名前です。Tablespace NameはRDFグラフを保存する表領域の名前です。Autonomous Databaseでは、通常これはDATAになります。

以上を設定し、ボタンCreate RDF networkをクリックします。


RDF networkが作成されました。と表示されれば、RDFネットワークRDF_NETWORKが作成されています。


Model NameGEO_DATAを入力し、ボタンCreate RDF modelをクリックします。

RDF modelが作成されました。と表示されるとRDFモデルGEO_DATAがRDFネットワークRDF_NETWORKに作成されています。


RDFモデルGEO_DATAにロードするデータを手元にダウンロードします。このデータはMatt Perryさんの記事でロードしているデータと同じものを使用します。



ダウンロードしたファイル2015–11–02-SportThing.node.sorted.nt.bz2はbzip2で圧縮されているので、これを解凍します。macOSではbzip2コマンドを使用しました。

bzip2 -d 2015-11-02-SportThing.node.sorted.nt.bz2

Uploadページを開きます。Fileとして解凍したファイル2015-11-02-SportThing.node.sorted.ntを選択します。Batch Linesはデフォルトの1000から変更は不要です。

以上でUpload Fileをクリックします。


ボタンUpload Fileをクリックすると、以下のコードがプロセスとして実行されます。

APEX_APPLICATION_TEMP_FILESにアップロードされたファイルをBatch Linesで指定した行数毎にまとめて、APEXコレクションNTRIPLESのメンバーにCLOB(列としてはCLOB001)として保存します。

N001(分割数=1000行単位の数)を降順でソートすると、N001の最大値が1226、列N002(これは行数)が338なので、ファイル2015–11–02-SportThing.node.sorted.ntに1225338の行が含まれていることが確認できます。


ボタンUpload Serverをクリックすると、APEXコレクションのメンバごとにN-TriplesをINSERT DATA { } で囲んでSPARQLのINSERT文にし、ORDS RDF Graph APIのSPARQL Updateを呼び出します。

大量のREST API(データのアップロードで1226回)を発行することになります。APEXの管理サービスのインスタンスの管理セキュリティより、最大Webサービス・リクエストを十分大きい値に変更しておく必要があります。


ボタンUpload Serverをクリックし、N-Triplesのデータのアップロードを開始します。アップロード処理はバックグラウンドで実行します。


データのアップロードの経過を対話モード・レポートに表示しています。

Totalworkはすべてのデータをアップロードするために必要なREST APIの発行回数、Sofarは現在までに成功したREST APIの呼び出し回数です。SofarがTotalworkの値に達するとデータのアップロードは完了です。

ボタンRefresh Reportをクリックすると、バックグラウンド処理の状況が更新されます。


ボタンUpload Serverが実行するプロセスに、以下のコードを記述しています。APEXコレクションに分割して保存されたN-TriplesのデータをINSERT DATA { }で囲んでREST APIを呼び出しています。REST APIの呼び出しが完了するごとに、APEX_BACKGROUND_PROCESS.SET_PROGRESSを呼び出して、進捗を更新しています。


環境に依存すると思いますが、REST API経由でのデータのアップロードに2時間超の時間がかかります。

Matt Perryさんの記事ではデータ・ファイルをオブジェクト・ストレージにアップロードし、それを外部表にしてsem_apis.load_into_staging_tableを呼び出してRDFモデルにN-Triplesをロードしています。多分、速度の面では圧倒的にアドバンテージがあると思います。大量にデータをロードする場合は、sem_apis.load_into_staging_tableの利用を検討することをお勧めします。

データのアップロードが途中で中断したときは、中断した時点でのSofarの値+1Start Partに設定した上でボタンUpload Serverをクリックすると、中断した時点からアップロードが再開されます。


StatusExecuted SuccessfullyになりSofarTotalworkが同じ数値になると、データのロードは完了です。


データのアップロードが完了した後に索引を作成します。DBA権限のあるユーザーで以下のコマンドを実行します。
BEGIN
  sem_apis.add_datatype_index(
    'http://www.opengis.net/ont/geosparql#wktLiteral',
    options=>'MATERIALIZE=T TOLERANCE=0.1 SRID=4326 DIMENSIONS=((LONGITUDE,-180,180) (LATITUDE,-90,90))',
    network_owner=>'Network Ownerのスキーマ名', 
    network_name=>'RDF_NETWORK');
END;
/
Autonomous Databaseの場合、管理者ユーザーADMINにてDatabase ActionsSQLワークシートで実行します。


続けて統計情報をアップデートします。
BEGIN
    sem_perf.gather_stats(network_owner=>'Network Ownerのスキーマ名', network_name=>'RDF_NETWORK');
END;
/

以上でGeoSPARQLの問合せができるようになりました。

Mapのページを開き、GeoSPARQLの問合せを発行します。


User Promptに「ニューヨーク市のテニスができる施設を一覧してください。」と入力し、ボタンGenerateをクリックします。SPARQL QueryにOpenAI GPT-4.1が生成した以下のレスポンスが設定されます。LLMのレスポンスなので、毎回同じになるとは限りません。
以下が「ニューヨーク市のテニスができる施設」を一覧するGeoSPARQLクエリです。
(与えられた例と同様、POLYGONはニューヨーク市の領域です。変更点はFILTER中の?sportの値のみです)

```sparql
PREFIX geovocab: <http://geovocab.org/geometry#>
PREFIX lgd: <http://linkedgeodata.org/ontology/>
PREFIX dbpedia: <http://dbpedia.org/resource/>
PREFIX ogc: <http://www.opengis.net/ont/geosparql#>
PREFIX ogcf: <http://www.opengis.net/def/function/geosparql/>

SELECT ?s ?sport ?label ?geom
WHERE {
  ?s geovocab:geometry/ogc:asWKT ?geom ;
     lgd:featuresSport ?sport ;
     rdfs:label ?label .
  FILTER(ogcf:sfWithin(?geom,
    "POLYGON((-74.2557 40.4961, -73.7004 40.4961, -73.7004 40.9153, -74.2557 40.9153, -74.2557 40.4961))"^^ogc:wktLiteral))
  FILTER (?sport = dbpedia:Tennis)
}
ORDER BY ?label
```

このクエリは、ニューヨーク市ポリゴン内で、スポーツ種別が`dbpedia:Tennis`である施設(?s)・その種別(?sport)・ラベル(?label)・WKTジオメトリ(?geom)を一覧表示します。


ボタンGenerateでは、APEXが標準で提供しているアクションAIによるテキストの生成を呼び出しています。

生成AI構成としてGenerate GeoSPARQL Queryを作成し、それを生成AI構成に設定しています。入力値はページ・アイテムP4_USER_PROMPTレスポンスの使用はページ・アイテムP4_SPARQL_QUERYです。

OpenAIのChat Completions APIの呼び出しとしては、構成Generate GeoSPARQL Queryにシステム・プロンプトが設定されていて、P4_SPARQL_QUERYがroleがuserのcontentになり、そのレスポンスであるcontentがP4_SPARQL_QUERYに設定されるという、単純な呼び出しが行われています。


生成AI構成Generate GeoSPARQL Queryでは、システム・プロンプトとして以下を設定しています。都市と運動施設の種別を検索条件とするGeoSPARQLクエリを生成するように指示しています。

問合せからGeoSPARQLのクエリを生成してください。PREFIXは以下です。
—
PREFIX geovocab: <http://geovocab.org/geometry#>
PREFIX lgd: <http://linkedgeodata.org/ontology/>
PREFIX dbpedia: <http://dbpedia.org/resource/>
PREFIX ogc: <http://www.opengis.net/ont/geosparql#>
PREFIX ogcf: <http://www.opengis.net/def/function/geosparql/>
—
選択するのは以下の?s ?geom ?sport ?labelです。

?s 
geovocab:geometry/ogc:asWKT ?geom
lgd:featuresSport ?sport
rdfs:label ?label

GeoSPARQLのクエリの例です。ニューヨーク市の領域をPOLYGONで与えて、そのPOLYGONに含まれる野球ができる施設の一覧です。

PREFIX geovocab: <http://geovocab.org/geometry#>
PREFIX lgd: <http://linkedgeodata.org/ontology/>
PREFIX dbpedia: <http://dbpedia.org/resource/>
PREFIX ogc: <http://www.opengis.net/ont/geosparql#>
PREFIX ogcf: <http://www.opengis.net/def/function/geosparql/>

SELECT ?s ?sport ?label ?geom
WHERE {
  ?s geovocab:geometry/ogc:asWKT ?geom ;
     lgd:featuresSport ?sport ;
     rdfs:label ?label .
  FILTER(ogcf:sfWithin(?geom,
    "POLYGON((-74.2557 40.4961, -73.7004 40.4961, -73.7004 40.9153, -74.2557 40.9153, -74.2557 40.4961))"^^ogc:wktLiteral))
  FILTER (?sport = dbpedia:Baseball)
}
ORDER BY ?label

ボタンUpdateをクリックし、生成AIのレスポンスに含まれているSPARQLを実行し、結果をマップと対話モード・レポートに表示します。

ボタンUpdateをクリックしたときに、プロセスとして以下のコードを実行します。SPARQLクエリのレスポンスとして受け取ったJSONを、APEXコレクションSPORTS_FACILITIESに入れています。マップおよび対話モード・レポートはこのAPEXコレクションSPORTS_FACILITIESをソースとして、マップのレイヤやレポートを表示しています。



東京都でも検索できたのは驚きました。


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

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