20. QGIS ServerとPython

20.1. はじめに

QGISサーバーについて更に学ぶには、 QGIS Server ガイド / マニュアル を読んでください。

QGIS Serverは3つの異なるものです:

  1. QGIS Serverライブラリ:OGCウェブサービスを作るためのAPIを提供するライブラリ

  2. QGIS Server FCGI: ウェブサーバーと共にOGCサービス(WMS、WFS、WCSなど)とOGC API (WFS3/OAPIF)を実装するFCGIバイナリアプリケーション qgis_mapserv.fcgi

  3. QGIS開発サーバー: OGCサービス(WMS、WFS、WCSなど)とOGC API (WFS3/OAPIF)を実装した開発サーバーバイナリーアプリケーション qgis_mapserver

クックブックのこの章では、最初のトピックに焦点を当て、QGIS Server API の使用方法を説明することで、Python を使ってサーバーの動作を拡張、強化、カスタマイズする方法や、QGIS Server API を使って QGIS サーバーを別のアプリケーションに組み込む方法を示します。

あなたが直面する可能性のある主なシナリオである、QGIS Serverの動作を変更したり、機能を拡張して新しいカスタムサービスやAPIを提供したりするには、いくつかの方法があります:

  • EMBEDDING → 別のPythonアプリケーションからQGIS Server APIを使用します

  • STANDALONE → QGIS ServerをスタンドアロンのWSGI/HTTPサービスとして実行します

  • FILTERS → フィルタープラグインでQGISサーバーを強化/カスタマイズします

  • SERVICES → 新しい SERVICE を追加します

  • OGC APIs → 新しい OGC API を追加します

埋め込みアプリケーションとスタンドアロンアプリケーションでは、他のPythonスクリプトやアプリケーションからQGIS Server Python APIを直接使用する必要があります。残りのオプションでは、標準のQGIS Serverバイナリアプリケーション(FCGIまたは開発サーバー)にカスタム機能を追加する場合に適しています: この場合、サーバーアプリケーション用のPythonプラグインを書いてカスタムフィルタ、サービス、またはAPIを登録する必要があります。

20.2. Server APIの基本

典型的なQGIS Serverアプリケーションの基本クラスは以下の通りです:

  • QgsServer サーバーのインスタンス(通常はアプリケーション全体で一つのインスタンス)

  • QgsServerRequest リクエストオブジェクト(通常はリクエスト毎に再生成されます。)

  • QgsServer.handleRequest(request, response) はリクエストを処理し、レスポンスを生成します

QGIS Server FCGIまたは開発サーバーのワークフローをまとめると、以下のようになります:

1initialize the QgsApplication
2create the QgsServer
3the main server loop waits forever for client requests:
4    for each incoming request:
5        create a QgsServerRequest request
6        create a QgsServerResponse response
7        call QgsServer.handleRequest(request, response)
8            filter plugins may be executed
9        send the output to the client

QgsServer.handleRequest(request, response) メソッドの内部では、フィルタプラグインのコールバックが呼び出され、 QgsServerRequestQgsServerResponseQgsServerInterface クラスを通してプラグインから利用できるようになります。

警告

QGISサーバークラスはスレッドセーフではないため、QGIS Server APIに基づいたスケーラブルなアプリケーションを構築する場合は、常にマルチプロセッシングモデルまたはコンテナを使用する必要があります。

20.3. STANDALONE 又は EMBEDDING

スタンドアロン・サーバー・アプリケーションや組み込みの場合は、上記のサーバー・クラスを直接使用し、クライアントとのすべてのHTTPプロトコルのやり取りを管理するウェブ・サーバー実装にまとめる必要があります。

QGIS Server APIの最小限の使用例(HTTP部分を除く)を以下に示します:

 1from qgis.core import QgsApplication
 2from qgis.server import *
 3app = QgsApplication([], False)
 4
 5# Create the server instance, it may be a single one that
 6# is reused on multiple requests
 7server = QgsServer()
 8
 9# Create the request by specifying the full URL and an optional body
10# (for example for POST requests)
11request = QgsBufferServerRequest(
12    'http://localhost:8081/?MAP=/qgis-server/projects/helloworld.qgs' +
13    '&SERVICE=WMS&REQUEST=GetCapabilities')
14
15# Create a response objects
16response = QgsBufferServerResponse()
17
18# Handle the request
19server.handleRequest(request, response)
20
21print(response.headers())
22print(response.body().data().decode('utf8'))
23
24app.exitQgis()

これは、QGISソースコードリポジトリの継続的な統合テストのために開発された完全なスタンドアロンアプリケーションの例で、さまざまなプラグインフィルタと認証スキームが紹介されています(本番用ではなく、テスト目的のみで開発されていますが、学習には興味深いものです): qgis_wrapped_server.py

20.4. サーバー・プラグイン

Server pythonプラグインは、QGIS Serverアプリケーションの起動時に一度だけロードされ、フィルタ、サービス、またはAPIを登録するために使用できます。

サーバープラグインの構造はデスクトップとよく似ており、QgsServerInterface オブジェクトがプラグインに提供され、プラグインはサーバーインターフェースによって公開されるメソッドの1つを使用して、対応するレジストリに1つ以上のカスタムフィルタ、サービス、またはAPIを登録することができます。

20.4.1. サーバー・フィルター・プラグイン

フィルタには3つの異なるフレーバーがあり、以下のクラスの1つをサブクラス化し、 QgsServerInterface の対応するメソッドを呼び出すことでインスタンス化できます:

フィルタ型

ベースクラス

QgsServerInterface 登録

I/O

QgsServerFilter

registerFilter()

アクセス制御

QgsAccessControlFilter

registerAccessControl()

キャッシュ

QgsServerCacheFilter

registerServerCache()

20.4.1.1. i/O フィルタ

I/Oフィルタは、コアサービス(WMS、WFSなど)のサーバー入力と出力(リクエストとレスポンス)を変更し、サービスのワークフローにあらゆる操作を加えることができます。例えば、選択したレイヤーへのアクセスを制限したり、XMLレスポンスにXSLスタイルシートを注入したり、生成されたWMS画像に透かしを追加したりすることができます。

この時点で、 server plugins API docs をざっと見ておくと役に立つかもしれません。

各フィルタは、3つのコールバックのうち少なくとも1つを実装する必要があります:

全てのフィルタはリクエスト/レスポンスオブジェクト(QgsRequestHandler)にアクセスすることができ、その全てのプロパティ(入力/出力)を操作し、例外を発生させることができます(後述するように、かなり特殊な方法で)。

これらのメソッドはすべて、その呼び出しが後続のフィルタに伝搬されるべきかどうかを示すブール値を返します。これらのメソッドのいずれかが False を返した場合はチェインが停止し、そうでない場合は呼び出しが次のフィルタに伝搬します。

以下は、典型的なリクエストをサーバがどのように処理し、フィルタのコールバックがいつ呼ばれるかを示す擬似コードです:

 1for each incoming request:
 2    create GET/POST request handler
 3    pass request to an instance of QgsServerInterface
 4    call onRequestReady filters
 5
 6    if there is not a response:
 7        if SERVICE is WMS/WFS/WCS:
 8            create WMS/WFS/WCS service
 9            call service’s executeRequest
10                possibly call onSendResponse for each chunk of bytes
11                sent to the client by a streaming services (WFS)
12        call onResponseComplete
13    request handler sends the response to the client

次の段落では、利用可能なコールバックを詳細に説明します。

20.4.1.1.1. onRequestReady

要求の準備ができたときに呼び出されます。受信URLとデータが解析され、コアサービス(WMS、WFSなど)スイッチに入る前に、これは入力を操作するなどのアクションを実行できるポイントです。

  • 認証/認可

  • リダイレクト

  • 特定のパラメーター(例えば、型名)を追加/除去

  • 例外を発生させる

SERVICE パラメーターを変更することでコアサービスを完全に置き換え、それによりコアサービスを完全にバイパスすることさえできるかもしれません(とはいえ、これはあまり意味がないということ)。

20.4.1.1.2. onSendResponse

これは、部分的な出力がレスポンスバッファから(つまり fcgi サーバが使用されている場合は FCGI stdout に)、さらにそこからクライアントにフラッシュされるたびに呼び出されます。これは(WFSのGetFeatureのように)巨大なコンテンツがストリームされる場合に発生します。この場合、 onSendResponse() が複数回呼び出される可能性があります。

レスポンスがストリームされない場合、 onSendResponse() は全く呼ばれないことに注意してください。

全ての場合において、最後の(あるいはユニークな)チャンクは onResponseComplete() の呼び出しの後にクライアントに送られます。

False を返すと、クライアントへのデータのフラッシュを防ぎます。プラグインがレスポンスから全てのチャンクを収集し、 onResponseComplete() でレスポンスの検査や変更を行いたい場合、これは望ましいことです。

20.4.1.1.3. onResponseComplete

これは、コアサービス(ヒットした場合)が処理を終了し、リクエスト をクライアントに送る準備ができたときに一度だけ呼ばれます。上述したように、このメソッドは最後の(あるいはユニークな)データチャンクがクライアントに送信される前に呼ばれます。ストリーミングサービスの場合、 onSendResponse() への複数の呼び出しが呼び出されている可能性があります。

onResponseComplete() は、新しいサービスの実装(WPSやカスタムサービス)を提供したり、コアサービスからの出力を直接操作する(例えば、WMS画像に透かしを追加する)のに最適な場所です。

False を返すと、次のプラグインが onResponseComplete() を実行できなくなりますが、いずれにせよ、レスポンスがクライアントに送信されなくなることに注意してください。

20.4.1.1.4. プラグインから例外を発生させる

このトピックについては、まだいくつかの仕事が残っています: 現在の実装では、QgsMapServiceExceptionのインスタンスに QgsRequestHandler プロパティを設定することで、処理される例外と処理されない例外を区別することができます。こうすることで、メインのC++コードは、処理されたpythonの例外をキャッチし、処理されなかった例外を無視(またはそれを記録)することができます。

このアプローチは、基本的に動作しますが、それは非常に「パイソン的」ではありません:より良いアプローチは、Pythonコードから例外を発生し、それらがそこで処理されるためにC ++ループに湧き上がるのを見ることでしょう。

20.4.1.1.5. サーバー・プラグインを書く

サーバプラグインは Pythonプラグインを開発する で説明されているような標準的なQGIS Pythonプラグインであり、追加の(あるいは代替の)インターフェースを提供します:一般的なQGISデスクトッププラグインは QgisInterface のインスタンスを通してQGISアプリケーションにアクセスすることができますが、サーバプラグインはQGIS Serverアプリケーションコンテキスト内で実行された時のみ QgsServerInterface にアクセスすることができます。

プラグインがサーバーインターフェイスを持っていることをQGIS Serverに認識させるには、特別なメタデータエントリが(metadata.txt に)必要です:

server=True

重要

server=True のメタデータが設定されているプラグインだけがQGIS Serverに読み込まれ、実行されます。

ここで説明した qgis3-server-vagrant のサンプルプラグインは github で公開されています(他にもたくさんあります)。いくつかのサーバプラグインは公式の QGIS plugins リポジトリ でも公開されています。

20.4.1.1.5.1. プラグインファイル

以下は、サーバープラグイン例のディレクトリ構造です。

1PYTHON_PLUGINS_PATH/
2  HelloServer/
3    __init__.py    --> *required*
4    HelloServer.py  --> *required*
5    metadata.txt   --> *required*
20.4.1.1.5.1.1. __init__.py

このファイルはPythonのインポートシステムによって要求されます。また、QGIS Serverはこのファイルに serverClassFactory() 関数を含めることを要求します。この関数は、サーバーが開始して、QGIS Serverにプラグインが読み込まれるときに呼び出されます。それは QgsServerInterface のインスタンスへの参照を受け取り、プラグインのクラスのインスタンスを返す必要があります。プラグインの例 __init__.py はこのようになっています:

def serverClassFactory(serverIface):
    from .HelloServer import HelloServerServer
    return HelloServerServer(serverIface)
20.4.1.1.5.1.2. HelloServer.py

魔法が起こると、これは魔法がどのように見えるかであるところである:(例 HelloServer.py

サーバプラグインは通常1つ以上のコールバックで構成され、 QgsServerFilter のインスタンスにパックされます。

QgsServerFilter は以下のコールバックのひとつ以上を実装しています:

次の例は、SERVICE パラメーターが "HELLO "に等しい場合に、HelloServer! を印字する最小のフィルタを実施しています:

 1class HelloFilter(QgsServerFilter):
 2
 3    def __init__(self, serverIface):
 4        super().__init__(serverIface)
 5
 6    def onRequestReady(self) -> bool:
 7        QgsMessageLog.logMessage("HelloFilter.onRequestReady")
 8        return True
 9
10    def onSendResponse(self) -> bool:
11        QgsMessageLog.logMessage("HelloFilter.onSendResponse")
12        return True
13
14    def onResponseComplete(self) -> bool:
15        QgsMessageLog.logMessage("HelloFilter.onResponseComplete")
16        request = self.serverInterface().requestHandler()
17        params = request.parameterMap()
18        if params.get('SERVICE', '').upper() == 'HELLO':
19            request.clear()
20            request.setResponseHeader('Content-type', 'text/plain')
21            # Note that the content is of type "bytes"
22            request.appendBody(b'HelloServer!')
23        return True

フィルターは、以下の例のように、serverIface に登録されなければなりません:

class HelloServerServer:
    def __init__(self, serverIface):
        serverIface.registerFilter(HelloFilter(serverIface), 100)

registerFilter()) の2番目のパラメータは、同じ名前のコールバックの順番を定義する優先度を設定します(優先度の低いものが最初に呼び出されます)。

3つのコールバックを使うことで、プラグインは様々な方法でサーバの入力や出力を操作することができます。プラグインのインスタンスはいつでも QgsServerInterface を通して QgsRequestHandler にアクセスすることができます。QgsRequestHandler クラスには、サーバのコア処理に入る前(:func:`requestReady`を使用する)や、リクエストがコアサービスで処理された後(:func:`sendResponse`を使用する)に入力パラメータを変更するために使用できるメソッドがたくさんあります。

次の例は、いくつかの一般的なユースケースをカバーします:

20.4.1.1.5.2. 入力を変更する

プラグイン例には、クエリ文字列から来る入力パラメータを変えるテスト例が含まれています。この例では、新しいパラメータが(すでにパースされた)``parameterMap`` に注入され、このパラメータはコアサービス(WMSなど)から見えるようになります:

 1class ParamsFilter(QgsServerFilter):
 2
 3    def __init__(self, serverIface):
 4        super(ParamsFilter, self).__init__(serverIface)
 5
 6    def onRequestReady(self) -> bool:
 7        request = self.serverInterface().requestHandler()
 8        params = request.parameterMap( )
 9        request.setParameter('TEST_NEW_PARAM', 'ParamsFilter')
10        return True
11
12    def onResponseComplete(self) -> bool:
13        request = self.serverInterface().requestHandler()
14        params = request.parameterMap( )
15        if params.get('TEST_NEW_PARAM') == 'ParamsFilter':
16            QgsMessageLog.logMessage("SUCCESS - ParamsFilter.onResponseComplete")
17        else:
18            QgsMessageLog.logMessage("FAIL    - ParamsFilter.onResponseComplete")
19        return True

これは、ログファイルに見るものの抽出物である:

1 src/core/qgsmessagelog.cpp: 45: (logMessage) [0ms] 2014-12-12T12:39:29 plugin[0] HelloServerServer - loading filter ParamsFilter
2 src/core/qgsmessagelog.cpp: 45: (logMessage) [1ms] 2014-12-12T12:39:29 Server[0] Server plugin HelloServer loaded!
3 src/core/qgsmessagelog.cpp: 45: (logMessage) [0ms] 2014-12-12T12:39:29 Server[0] Server python plugins loaded
4 src/mapserver/qgshttprequesthandler.cpp: 547: (requestStringToParameterMap) [1ms] inserting pair SERVICE // HELLO into the parameter map
5 src/mapserver/qgsserverfilter.cpp: 42: (onRequestReady) [0ms] QgsServerFilter plugin default onRequestReady called
6 src/core/qgsmessagelog.cpp: 45: (logMessage) [0ms] 2014-12-12T12:39:29 plugin[0] SUCCESS - ParamsFilter.onResponseComplete

強調表示された行の「SUCCESS」の文字列は、プラグインがテストに合格したことを示しています。

同じテクニックを使えば、コアサービスの代わりにカスタムサービスを使うこともできます: 例えば、WFS SERVICE リクエストやその他のコアリクエストを、SERVICE パラメータを別のものに変更するだけで、コアサービスをスキップすることができます。そして、カスタム結果を出力に注入してクライアントに送ることができます(これについては後述します)。

Tip

本当にカスタムサービスを実装したい場合は、 QgsService をサブクラスにして、 registerFilter()registerService(service) を呼び出してサービスを登録することをお勧めします。

20.4.1.1.5.3. 出力を変更または置き換えする

透かしフィルタの例は、WMSコアサービスによって作成されたWMS画像の上に透かし画像を加算した新たな画像でWMS出力を置き換える方法を示しています:

 1from qgis.server import *
 2from qgis.PyQt.QtCore import *
 3from qgis.PyQt.QtGui import *
 4
 5class WatermarkFilter(QgsServerFilter):
 6
 7    def __init__(self, serverIface):
 8        super().__init__(serverIface)
 9
10    def onResponseComplete(self) -> bool:
11        request = self.serverInterface().requestHandler()
12        params = request.parameterMap( )
13        # Do some checks
14        if (params.get('SERVICE').upper() == 'WMS' \
15                and params.get('REQUEST').upper() == 'GETMAP' \
16                and not request.exceptionRaised() ):
17            QgsMessageLog.logMessage("WatermarkFilter.onResponseComplete: image ready %s" % request.parameter("FORMAT"))
18            # Get the image
19            img = QImage()
20            img.loadFromData(request.body())
21            # Adds the watermark
22            watermark = QImage(os.path.join(os.path.dirname(__file__), 'media/watermark.png'))
23            p = QPainter(img)
24            p.drawImage(QRect( 20, 20, 40, 40), watermark)
25            p.end()
26            ba = QByteArray()
27            buffer = QBuffer(ba)
28            buffer.open(QIODevice.WriteOnly)
29            img.save(buffer, "PNG" if "png" in request.parameter("FORMAT") else "JPG")
30            # Set the body
31            request.clearBody()
32            request.appendBody(ba)
33        return True

この例では、SERVICE パラメータ値がチェックされ、受信リクエストが WMS GETMAP であり、以前に実行されたプラグインまたはコアサービス(この場合はWMS)によって例外が設定されていない場合、WMSによって生成された画像が出力バッファから取得され、透かし画像が追加されます。最後のステップは、出力バッファをクリアし、新しく生成された画像に置き換えることです。実際の状況では、PNGやJPGだけをサポートするのではなく、要求された画像タイプもチェックする必要があることに注意してください。

20.4.1.2. アクセス制御フィルタ

アクセス制御フィルタは、開発者がどのレイヤー、機能、属性にアクセスできるかを細かく制御できるようにします。アクセス制御フィルタには、以下のコールバックを実装できます:

20.4.1.2.1. プラグインファイル

以下は、プラグイン例のディレクトリ構造です:

1PYTHON_PLUGINS_PATH/
2  MyAccessControl/
3    __init__.py    --> *required*
4    AccessControl.py  --> *required*
5    metadata.txt   --> *required*
20.4.1.2.1.1. __init__.py

このファイルはPythonのインポートシステムによって必要とされます。全てのQGISサーバプラグインと同様に、このファイルには serverClassFactory() 関数が含まれており、プラグインが起動時にQGIS Serverにロードされる際に呼び出されます。この関数は QgsServerInterface のインスタンスへの参照を受け取り、プラグインのクラスのインスタンスを返す必要があります。プラグインの例 __init__.py はこのようになります:

def serverClassFactory(serverIface):
    from MyAccessControl.AccessControl import AccessControlServer
    return AccessControlServer(serverIface)
20.4.1.2.1.2. AccessControl.py
 1class AccessControlFilter(QgsAccessControlFilter):
 2
 3    def __init__(self, server_iface):
 4        super().__init__(server_iface)
 5
 6    def layerFilterExpression(self, layer):
 7        """ Return an additional expression filter """
 8        return super().layerFilterExpression(layer)
 9
10    def layerFilterSubsetString(self, layer):
11        """ Return an additional subset string (typically SQL) filter """
12        return super().layerFilterSubsetString(layer)
13
14    def layerPermissions(self, layer):
15        """ Return the layer rights """
16        return super().layerPermissions(layer)
17
18    def authorizedLayerAttributes(self, layer, attributes):
19        """ Return the authorised layer attributes """
20        return super().authorizedLayerAttributes(layer, attributes)
21
22    def allowToEdit(self, layer, feature):
23        """ Are we authorised to modify the following geometry """
24        return super().allowToEdit(layer, feature)
25
26    def cacheKey(self):
27        return super().cacheKey()
28
29class AccessControlServer:
30
31   def __init__(self, serverIface):
32      """ Register AccessControlFilter """
33      serverIface.registerAccessControl(AccessControlFilter(serverIface), 100)

この例では全員に完全なアクセス権を与えています。

誰がログオンしているかを知るのはこのプラグインの役割です。

これらすべての方法で私達は、レイヤーごとの制限をカスタマイズできるようにするには、引数のレイヤーを持っています。

20.4.1.2.2. layerFilterExpression

結果を制限する式を追加するために使用されます。

例えば、属性 roleuser に等しい地物に制限します。

def layerFilterExpression(self, layer):
    return "$role = 'user'"
20.4.1.2.3. layerFilterSubsetString

以前よりも同じですが、(データベース内で実行) SubsetString を使用

例えば、属性 roleuser に等しい地物に制限します。

def layerFilterSubsetString(self, layer):
    return "role = 'user'"
20.4.1.2.4. layerPermissions

レイヤーへのアクセスを制限します。

プロパティを持つ LayerPermissions() 型のオブジェクトを返します:

  • canReadGetCapabilities に表示され、読み取りアクセスが可能になります。

  • canInsert で新しい地物を挿入できるようになります。

  • canUpdate で地物を更新できるようになります。

  • canDelete で地物を削除できるようになります。

例えば、すべてを読み取り専用アクセスに制限するには:

1def layerPermissions(self, layer):
2    rights = QgsAccessControlFilter.LayerPermissions()
3    rights.canRead = True
4    rights.canInsert = rights.canUpdate = rights.canDelete = False
5    return rights
20.4.1.2.5. authorizedLayerAttributes

属性の特定のサブセットの可視性を制限するために使用します。

引数の属性が表示属性の現在のセットを返します。

例えば、role 属性を隠します:

def authorizedLayerAttributes(self, layer, attributes):
    return [a for a in attributes if a != "role"]
20.4.1.2.6. allowToEdit

これは、地物のサブセットに編集を制限するために使用されます。

これは、 WFS-Transaction プロトコルで使用されています。

例えば、role という属性に user という値を持つ地物だけを編集できるようにします:

def allowToEdit(self, layer, feature):
    return feature.attribute('role') == 'user'
20.4.1.2.7. cacheKey

QGIS Serverは機能のキャッシュを維持し、roleごとにキャッシュを持つには、このメソッドでroleを返します。また、キャッシュを完全に無効にするには None を返します。

20.4.2. カスタムサービス

QGIS Serverでは、WMS、WFS、WCSなどのコアサービスは QgsService のサブクラスとして実装されています。

クエリ文字列パラメータ SERVICE がサービス名にマッチした時に実行される新しいサービスを実装するには、独自の QgsService を実装し、その registerService(service) を呼び出して serviceRegistry() にサービスを登録します。

以下は CUSTOM という名前のカスタムサービスの例です:

 1from qgis.server import QgsService
 2from qgis.core import QgsMessageLog
 3
 4class CustomServiceService(QgsService):
 5
 6    def __init__(self):
 7        QgsService.__init__(self)
 8
 9    def name(self):
10        return "CUSTOM"
11
12    def version(self):
13        return "1.0.0"
14
15    def executeRequest(self, request, response, project):
16        response.setStatusCode(200)
17        QgsMessageLog.logMessage('Custom service executeRequest')
18        response.write("Custom service executeRequest")
19
20
21class CustomService():
22
23    def __init__(self, serverIface):
24        serverIface.serviceRegistry().registerService(CustomServiceService())

20.4.3. カスタムAPI

QGIS Serverでは、OAPIF(別名WFS3)のようなコアOGC APIは QgsServerOgcApiHandler サブクラスのコレクションとして実装されており、 QgsServerOgcApi (またはその親クラス QgsServerApi) のインスタンスに登録されます。

urlパスが特定のURLにマッチした時に実行される新しいAPIを実装するには、独自の QgsServerOgcApiHandler インスタンスを実装し、それらを QgsServerOgcApiregisterApi(api) を呼び出してAPIを登録します。

以下は、URLに /customapi が含まれる場合に実行されるカスタムAPIの例です:

 1import json
 2import os
 3
 4from qgis.PyQt.QtCore import QBuffer, QIODevice, QTextStream, QRegularExpression
 5from qgis.server import (
 6    QgsServiceRegistry,
 7    QgsService,
 8    QgsServerFilter,
 9    QgsServerOgcApi,
10    QgsServerQueryStringParameter,
11    QgsServerOgcApiHandler,
12)
13
14from qgis.core import (
15    QgsMessageLog,
16    QgsJsonExporter,
17    QgsCircle,
18    QgsFeature,
19    QgsPoint,
20    QgsGeometry,
21)
22
23
24class CustomApiHandler(QgsServerOgcApiHandler):
25
26    def __init__(self):
27        super(CustomApiHandler, self).__init__()
28        self.setContentTypes([QgsServerOgcApi.HTML, QgsServerOgcApi.JSON])
29
30    def path(self):
31        return QRegularExpression("/customapi")
32
33    def operationId(self):
34        return "CustomApiXYCircle"
35
36    def summary(self):
37        return "Creates a circle around a point"
38
39    def description(self):
40        return "Creates a circle around a point"
41
42    def linkTitle(self):
43        return "Custom Api XY Circle"
44
45    def linkType(self):
46        return QgsServerOgcApi.data
47
48    def handleRequest(self, context):
49        """Simple Circle"""
50
51        values = self.values(context)
52        x = values['x']
53        y = values['y']
54        r = values['r']
55        f = QgsFeature()
56        f.setAttributes([x, y, r])
57        f.setGeometry(QgsCircle(QgsPoint(x, y), r).toCircularString())
58        exporter = QgsJsonExporter()
59        self.write(json.loads(exporter.exportFeature(f)), context)
60
61    def templatePath(self, context):
62        # The template path is used to serve HTML content
63        return os.path.join(os.path.dirname(__file__), 'circle.html')
64
65    def parameters(self, context):
66        return [QgsServerQueryStringParameter('x', True, QgsServerQueryStringParameter.Type.Double, 'X coordinate'),
67                QgsServerQueryStringParameter(
68                    'y', True, QgsServerQueryStringParameter.Type.Double, 'Y coordinate'),
69                QgsServerQueryStringParameter('r', True, QgsServerQueryStringParameter.Type.Double, 'radius')]
70
71
72class CustomApi():
73
74    def __init__(self, serverIface):
75        api = QgsServerOgcApi(serverIface, '/customapi',
76                            'custom api', 'a custom api', '1.1')
77        handler = CustomApiHandler()
78        api.registerHandler(handler)
79        serverIface.serviceRegistry().registerApi(api)