20. QGIS server et Python
20.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: a FCGI binary application
qgis_mapserv.fcgi
that together with a web server implements a set of OGC services (WMS, WFS, WCS etc.) and OGC APIs (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.
20.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.
20.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()
Here is a complete standalone application example developed for the continuous integrations testing on QGIS source code repository, it showcases a wide set of different plugin filters and authentication schemes (not mean for production because they were developed for testing purposes only but still interesting for learning): qgis_wrapped_server.py
20.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.
20.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 |
20.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).
All these methods return a boolean value indicating if the call should be propagated to the subsequent
filters. If one of these method returns False
then the chain stop, otherwise the call will propagate
to the next filter.
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 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
Les paragraphes qui suivent décrivent les fonctions de rappel disponibles en détails.
20.4.1.1.1. onRequestReady
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).
20.4.1.1.2. onSendResponse
This is called whenever any partial output is flushed from response buffer (i.e to FCGI stdout
if the fcgi server is used) and from there, to the client.
This occurs when huge content is streamed (like WFS GetFeature). In this case
onSendResponse()
may be called multiple times.
Note that if the response is not streamed, then onSendResponse()
will not be called at all.
In all case, the last (or unique) chunk will be sent to client after a call to
onResponseComplete()
.
Returning False
will prevent flushing of data to the client. This is desirable when a plugin
wants to collect all chunks from a response and examine or change the response in
onResponseComplete()
.
20.4.1.1.3. onResponseComplete
This is called once when core services (if hit) finish their process and the
request is ready to be sent to the client.
As discussed above, this method will be called before the last (or unique) chunk of
data is sent to the client.
For streaming services, multiple calls to onSendResponse()
might have been called.
onResponseComplete()
is the
ideal place to provide new services implementation
(WPS or custom services) and to perform direct manipulation of the output coming
from core services (for example to add a watermark upon a WMS image).
Note that returning False
will prevent the next plugins to execute
onResponseComplete()
but, in any case, prevent response to be sent to the client.
20.4.1.1.4. 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.
20.4.1.1.5. É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.
The qgis3-server-vagrant example plugin discussed here (with many more) is available on github, a few server plugins are also published in the official QGIS plugins repository.
20.4.1.1.5.1. 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*
20.4.1.1.5.1.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)
20.4.1.1.5.1.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 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
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 :
20.4.1.1.5.2. 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 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
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: (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
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)
20.4.1.1.5.3. 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 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
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.
20.4.1.2. 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 :
20.4.1.2.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*
20.4.1.2.1.1. __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)
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)
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.
20.4.1.2.2. layerFilterExpression
Used to add an Expression to limit the results.
For example, to limit to features where the attribute role
is equal to user
.
def layerFilterExpression(self, layer):
return "$role = 'user'"
20.4.1.2.3. layerFilterSubsetString
Comme le point précédent mais utilise SubsetString
(exécuté au niveau de la base de données).
For example, to limit to features where the attribute role
is equal to user
.
def layerFilterSubsetString(self, layer):
return "role = 'user'"
20.4.1.2.4. 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é.
For example, to limit everything on read only access:
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.6. allowToEdit
Il permet de limiter l’édition à un sous-ensemble d’entités.
Il est utilisé dans le protocole WFS-Transaction
.
For example, to be able to edit only feature that has the attribute role
with the value user
:
def allowToEdit(self, layer, feature):
return feature.attribute('role') == 'user'
20.4.1.2.7. cacheKey
QGIS Server maintains a cache of the capabilities then to have a cache
per role you can return the role in this method. Or return None
to completely disable the cache.
20.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
.
To implement a new service that will be executed when the query string parameter SERVICE
matches the service name,
you can implement your own QgsService
and register your service on the serviceRegistry()
by calling its registerService(service)
.
Here is an example of a custom service named 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 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
).
To implement a new API that will be executed when the url path matches a certain URL,
you can implement your own QgsServerOgcApiHandler
instances,
add them to an QgsServerOgcApi
and register
the API on the serviceRegistry()
by calling its 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)