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 :
Bibliothèque QGIS Server : une bibliothèque qui fournit une API pour la création de services web OGC
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)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 :
QgsServer
l’instance du serveur (typiquement une seule instance pour toute la durée de vie de l’application)QgsServerRequest
l’objet de la requête (généralement recréé sur chaque requête)QgsServer.handleRequest(request, response)
traite la requête et remplit la réponse
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 |
||
Contrôle d’accès |
||
Cache |
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 leGetCapabilities
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.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)