19. QGIS server et Python

19.1. Introduction

Pour en savoir plus sur QGIS Server, lisez le Guide/manuel de QGIS Serveur.

QGIS Server, c’est trois choses différentes :

  1. Bibliothèque QGIS Server : une bibliothèque qui fournit une API pour la création de services web OGC

  2. QGIS Server FCGI : une application binaire FCGI qgis_maserv.fcgi qui, avec un serveur web, implémente un ensemble de services OGC (WMS, WFS, WCS etc.) et d’API OGC (WFS3/OAPIF)

  3. Developpement QGIS Server : une application binaire de serveur de développement qgis_mapserver qui implémente un ensemble de services OGC (WMS, WFS, WCS etc.) et des API OGC (WFS3/OAPIF)

Ce chapitre du livre de cuisine se concentre sur le premier sujet et, en expliquant l’utilisation de l’API QGIS Server, il montre comment il est possible d’utiliser Python pour étendre, améliorer ou personnaliser le comportement du serveur ou comment utiliser l’API QGIS Server pour intégrer QGIS Server dans une autre application.

Il existe plusieurs façons de modifier le comportement de QGIS Server ou d’étendre ses capacités pour offrir de nouveaux services ou API personnalisés, voici les principaux scénarios auxquels vous pouvez être confrontés :

  • EMBEDDING → Utiliser l’API QGIS Server depuis une autre application Python

  • STANDALONE → Exécuter QGIS Server comme un service WSGI/HTTP autonome

  • FILTRES → Améliorer/personnaliser QGIS Server avec des plugins de filtrage

  • SERVICES → Ajouter un nouveau SERVICE

  • OGC APIs → Ajouter une nouvelle API OGC

Les applications intégrées ou autonomes nécessitent l’utilisation de l’API Python de QGIS Server directement à partir d’un autre script ou application Python. Les autres options sont mieux adaptées lorsque vous souhaitez ajouter des fonctionnalités personnalisées à une application binaire standard de QGIS Server (FCGI ou serveur de développement) : dans ce cas, vous devrez écrire un plugin Python pour l’application serveur et enregistrer vos filtres, services ou API personnalisés.

19.2. Principes de base de l’API du serveur

Les classes fondamentales impliquées dans une application typique de QGIS Server sont les suivantes :

Le flux de travail QGIS Server FCGI ou serveur de développement peut être résumé comme suit :

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

Dans la méthode QgsServer.handleRequest(request, response) les callbacks des plugins de filtre sont appelés et QgsServerRequest et QgsServerResponse sont mis à la disposition des plugins par le biais de la QgsServerInterface.

Avertissement

Les classes QGIS Server ne sont pas sûres pour les threads, vous devez toujours utiliser un modèle de multitraitement ou des conteneurs lorsque vous construisez des applications évolutives basées sur l’API du serveur QGIS.

19.3. Autonome ou intégré

Pour les applications serveur autonomes ou integre, vous devrez utiliser directement les classes de serveur mentionnées ci-dessus, en les intégrant dans une implémentation de serveur web qui gère toutes les interactions du protocole HTTP avec le client.

Voici un exemple minimal d’utilisation de l’API QGIS Server (sans la partie 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()

Voici un exemple complet d’application autonome développée pour le test continu des intégrations sur le dépôt de code source de QGIS, il présente un large ensemble de filtres de plugins et de schémas d’authentification différents (non destinés à la production car ils ont été développés à des fins de test uniquement mais toujours intéressants pour l’apprentissage) : https://github.com/qgis/QGIS/blob/release-3_16/tests/src/python/qgis_wrapped_server.py

19.4. Plugins de serveur

Les plugins python du serveur sont chargés une fois lorsque l’application QGIS Server démarre et peuvent être utilisés pour enregistrer des filtres, des services ou des API.

La structure d’un plugin serveur est très similaire à son homologue de bureau, un objet QgsServerInterface est mis à la disposition des plugins et ceux-ci peuvent enregistrer un ou plusieurs filtres, services ou API personnalisés dans le registre correspondant en utilisant une des méthodes exposées par l’interface serveur.

19.4.1. Plugins pour filtres de serveur

Les filtres existent en trois possibilite différentes et peuvent être instanciés en sous-classant l’une des classes ci-dessous et en appelant la méthode correspondante de QgsServerInterface :

Type de filtre

Classe de base

Enregistrement de QgsServerInterface

I/O

QgsServerFilter

registerFilter()

Contrôle d’accès

QgsAccessControlFilter

registerAccessControl()

Cache

QgsServerCacheFilter

registerServerCache()

19.4.1.1. Filtres I/O

Les filtres I/O peuvent modifier l’entrée et la sortie du serveur (la demande et la réponse) des services de base (WMS, WFS, etc.), ce qui permet d’effectuer tout type de manipulation du flux de travail des services. Il est possible, par exemple, de restreindre l’accès à des couches sélectionnées, d’injecter une feuille de style XSL dans la réponse XML, d’ajouter un filigrane à une image WMS générée, etc.

A partir de là, il vous sera peut-être utile de jeter un coup d’oeil rapide à l’API des plugins server.

Chaque filtre doit mettre en œuvre au moins un des trois rappels :

Tous les filtres ont accès à l’objet requête/réponse (QgsRequestHandler) et peuvent manipuler toutes ses propriétés (entrée/sortie) et lever des exceptions (mais d’une manière assez particulière comme nous le verrons plus loin).

Voici le pseudo-code montrant comment le serveur traite une requête typique et quand les rappels du filtre sont appelés :

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

Les paragraphes qui suivent décrivent les fonctions de rappel disponibles en détails.

19.4.1.1.1. requestReady

Cette fonction est appelée lorsque la requête est prêt: l’URL entrante et ses données ont été analysées et juste avant de passer la main aux services principaux (WMS, WFS, etc.), c’est le point où vous pouvez manipuler l’entrée et dérouler des actions telles que:

  • l’authentification/l’autorisation

  • les redirections

  • l’ajout/suppression de certains paramètres (les noms de type par exemple)

  • le déclenchement d’exceptions

Vous pouvez également substituer l’intégralité d’un service principal en modifiant le paramètre SERVICE et complètement outrepasser le service (ce qui n’a pas beaucoup d’intérêt).

19.4.1.1.2. sendResponse

Il est appelé chaque fois qu’une sortie est envoyée à FCGI stdout (et de là au client). Cela se fait normalement après que les services principaux aient terminé leur processus et après que le hook responseComplete ait été appelé, mais dans quelques cas, le XML peut devenir si énorme qu’une implémentation de streaming XML est nécessaire (WFS GetFeature est l’un d’entre eux). Dans ce cas, au lieu d’un appel unique à sendResponse(), la méthode peut exceptionnellement être appelée plusieurs fois avant que la réponse ne soit complète, et dans ce cas (et seulement dans ce cas) elle est également appelée avant responseComplete().

sendResponse() est le meilleur endroit pour manipuler directement la sortie du service de base et même si responseComplete() est généralement aussi une option, sendResponse() est la seule option viable en cas de services de flux.

19.4.1.1.3. responseComplete

Il est appelé une fois lorsque les services centraux (s’ils sont touchés) ont terminé leur processus et que la demande est prête à être envoyée au client. Comme indiqué ci-dessus, il est normalement appelé avant sendResponse() sauf pour les services de streaming (ou autres filtres de plugin) qui auraient pu appeler sendResponse() plus tôt.

responseComplete() est l’endroit idéal pour fournir l’implémentation de nouveaux services (WPS ou services personnalisés) et pour effectuer une manipulation directe de la sortie provenant des services de base (par exemple pour ajouter un filigrane sur une image WMS).

19.4.1.2. Lever les exceptions d’un plugin

Un certain travail reste à faire sur ce sujet : l’implémentation actuelle peut distinguer les exceptions gérées et non gérées en définissant une propriété QgsRequestHandler à une instance de QgsMapServiceException, de cette façon le code C++ principal peut attraper les exceptions python gérées et ignorer les exceptions non gérées (ou mieux : les enregistrer).

Cette approche fonctionne globalement mais elle n’est pas très « pythonesque »: une meilleure approche consisterait à déclencher des exceptions depuis le code Python et les faire remonter dans la boucle principale C++ pour y être traitées.

19.4.1.3. Écriture d’une extension serveur

Un plugin serveur est un plugin Python QGIS standard tel que décrit dans Développer des extensions Python, qui fournit juste une interface supplémentaire (ou alternative) : un plugin de bureau QGIS typique a accès à l’application QGIS par le biais de la QgisInterface, un plugin serveur a seulement accès à une QgsServerInterface lorsqu’il est exécuté dans le contexte de l’application QGIS Server.

Pour que QGIS serveur sache qu’un plugin a une interface serveur, une entrée spéciale de métadonnées est nécessaire (dans metadata.txt) :

server=True

Important

Seuls les plugins qui ont le jeu de métadonnées server=True seront chargés et exécutés par QGIS Server.

L’exemple de plugin présenté ici (avec beaucoup d’autres) est disponible sur github à l’adresse https://github.com/elpaso/qgis3-server-vagrant/tree/master/resources/web/plugins, quelques plugins de serveur sont également publiés dans le dépôt officiel de plugins QGIS.

19.4.1.4. Fichiers de l’extension

Vous pouvez voir ici la structure du répertoire de notre exemple d’extension pour serveur.

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

Ce fichier est requis par le système d’importation de Python. De plus, QGIS Server exige que ce fichier contienne une fonction serverClassFactory(), qui est appelée lorsque le plugin est chargé dans QGIS Server au démarrage du serveur. Elle reçoit une référence à l’instance de QgsServerInterface et doit retourner une instance de la classe de votre plugin. Voici à quoi ressemble le plugin d’exemple __init__.py :

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

C’est l’endroit où tout se passe et voici à quoi il devrait ressembler : (ex. HelloServer.py)

Un plugin serveur consiste généralement en un ou plusieurs callbacks regroupés dans les instances d’un QgsServerFilter.

Chaque QgsServerFilter implémente un ou plusieurs des callbacks suivants :

L’exemple qui suit implémente un filtre minimaliste qui affiche HelloServer! pour le cas où le paramètre SERVICE vaut “HELLO” :

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

Les filtres doivent être enregistrés dans la serverIface comme dans l’exemple suivant :

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

Le second paramètre de registerFilter() fixe une priorité qui définit l’ordre des rappels ayant le même nom (la priorité la plus basse est invoquée en premier).

En utilisant les trois rappels, les plugins peuvent manipuler l’entrée et/ou la sortie du serveur de nombreuses manières différentes. À chaque instant, l’instance du plugin a accès à la QgsRequestHandler par le biais de la QgsServerInterface. La classe QgsRequestHandler a de nombreuses méthodes qui peuvent être utilisées pour modifier les paramètres d’entrée avant d’entrer dans le traitement de base du serveur (en utilisant requestReady()) ou après que la requête ait été traitée par les services de base (en utilisant sendResponse()).

Les exemples suivants montrent quelques cas d’utilisation courants :

19.4.1.4.3. Modifier la couche en entrée

L’exemple de plugin contient un exemple de test qui modifie les paramètres d’entrée provenant de la chaîne de requête, dans cet exemple un nouveau paramètre est injecté dans le « parameterMap » (déjà analysé), ce paramètre est ensuite visible par les services centraux (WMS etc.), à la fin du traitement des services centraux nous vérifions que le paramètre est toujours là :

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

Ceci est un extrait de ce que vous pouvez voir dans le fichier log:

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: (requestReady) [0ms] QgsServerFilter plugin default requestReady called
6 src/core/qgsmessagelog.cpp: 45: (logMessage) [0ms] 2014-12-12T12:39:29 plugin[0] SUCCESS - ParamsFilter.responseComplete

Sur la ligne en surbrillance, la chaîne « SUCCESS » indique que le plugin a réussi le test.

La même technique peut être employée pour utiliser un service personnalisé à la place d’un service principal : vous pouvez par exemple sauter une requête WFS SERVICE ou n’importe quelle requête principale en modifiant le paramètre SERVICE par quelque-chose de différent et le service principal ne serait alors pas lancé. Vous pourrez ensuite injecter vos résultats personnalisés dans la sortie et les renvoyer au client (ceci est expliqué ci-dessous).

Astuce

Si vous voulez vraiment implémenter un service personnalisé, il est recommandé de sous-classer QgsService et d’enregistrer votre service sur registerFilter() en appelant son registerService(service)

19.4.1.4.4. Modifier ou remplacer la couche en sortie

L’exemple du filtre de filigrane montre comment remplacer la sortie WMS avec une nouvelle image obtenue par l’ajout d’un filigrane plaqué sur l’image WMS générée par le service principal 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 responseComplete(self):
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.responseComplete: 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)

Dans cet exemple, la valeur du paramètre SERVICE est vérifiée et si la demande entrante est un WMS GETMAP et qu’aucune exception n’a été définie par un plugin exécuté précédemment ou par le service central (WMS dans ce cas), l’image générée par le WMS est récupérée dans le tampon de sortie et l’image en filigrane est ajoutée. L’étape finale consiste à effacer le tampon de sortie et à le remplacer par l’image nouvellement générée. Veuillez noter que dans une situation réelle, nous devons également vérifier le type d’image demandé au lieu de supporter uniquement les PNG ou JPG.

19.4.1.5. Filtres de contrôle d’accès

Les filtres de contrôle d’accès donnent au développeur un contrôle fin sur les couches, les entités et les attributs auxquels il peut accéder, les rappels suivants peuvent être mis en œuvre dans un filtre de contrôle d’accès :

19.4.1.5.1. Fichiers de l’extension

Voici la structure des répertoires de notre exemple de plugin :

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

Ce fichier est requis par le système d’importation de Python. Comme pour tous les plugins QGIS Server , ce fichier contient une fonction serverClassFactory(), qui est appelée lorsque le plugin est chargé dans QGIS Server au démarrage. Elle reçoit une référence à une instance de QgsServerInterface et doit retourner une instance de la classe de votre plugin. Voici à quoi ressemble le plugin d’exemple __init__.py :

def serverClassFactory(serverIface):
    from MyAccessControl.AccessControl import AccessControlServer
    return AccessControlServer(serverIface)
19.4.1.5.3. 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)

Cet exemple donne un accès total à tout le monde.

C’est le rôle de l’extension de connaître qui est connecté dessus.

Pour toutes ces méthodes nous avons la couche passée en argument afin de personnaliser la restriction par couche.

19.4.1.5.4. layerFilterExpression

Utilisé pour ajouter une expression pour limiter les résultats, ex:

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

Pour limiter aux entités où l’attribut role vaut « user ».

19.4.1.5.5. layerFilterSubsetString

Comme le point précédent mais utilise SubsetString (exécuté au niveau de la base de données).

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

Pour limiter aux entités où l’attribut role vaut « user ».

19.4.1.5.6. layerPermissions

Limiter l’accès à la couche.

Retourne un objet de type LayerPermissions(), qui a les propriétés :

  • canRead voir dans le GetCapabilities et acces lecture seule.

  • canInsert pour pouvoir insérer une nouvelle entité .

  • canUpdate pour pouvoir mettre à jour une entité.

  • canDelete pour pouvoir supprimer une entité.

Exemple :

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

Pour tout limiter à un accès en lecture seule.

19.4.1.5.7. authorizedLayerAttributes

Utilisé pour limiter la visibilité d’un sous-groupe d’attribut spécifique.

L’argument attributes renvoie la liste des attributs réellement visibles.

Exemple :

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

Cache l’attribut “role”.

19.4.1.5.8. allowToEdit

Il permet de limiter l’édition à un sous-ensemble d’entités.

Il est utilisé dans le protocole WFS-Transaction.

Exemple :

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

Pour limiter l’édition aux entités dont l’attribut role contient la valeur user.

19.4.1.5.9. cacheKey

QGIS Server conserve un cache du capabilties donc pour avoir un cache par rôle vous pouvez retourner le rôle dans cette méthode. Ou retourner None pour complètement désactiver le cache.

19.4.2. Services personnalisés

Dans QGIS Server, les services de base tels que WMS, WFS et WCS sont implémentés en tant que sous-classes de QgsService.

Pour implémenter un nouveau service qui sera exécuté lorsque le paramètre de la chaîne de requête SERVICE correspondra au nom du service, vous pouvez implémenter votre propre QgsService et enregistrer votre service sur le serviceRegistry() en appelant son registerService(service).

Voici un exemple d’un service de personnalis appelé 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())

19.4.3. API personnalisées

Dans QGIS Server, les API OGC de base telles que OAPIF (alias WFS3) sont implémentées sous forme de collections de QgsServerOgcApiHandler sous-classes qui sont enregistrées dans une instance de QgsServerOgcApi (ou sa classe parente QgsServerApi).

Pour implémenter une nouvelle API qui sera exécutée lorsque le chemin de l’URL correspondra à une certaine URL, vous pouvez implémenter vos propres instances QgsServerOgcApiHandler, les ajouter à une instance QgsServerOgcApi et enregistrez l’API sur le serviceRegistry() en appelant son registerApi(api).

Voici un exemple d’API personnalisée qui sera exécutée lorsque l’URL contient « /customapi » :

 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)