19. QGIS server et Python¶
19.1. Introduction¶
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 → Run QGIS Server as a standlone WSGI/HTTP service
FILTRES → Améliorer/personnaliser QGIS Server avec des plugins de filtrage
SERVICES → Ajouter un nouveau SERVICE
OGC APIs → Ajouter une nouvelle API OGC
L’intégration et les applications 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)QgsServerResponse
l’objet de réponse (généralement recréé à 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 :
1 2 3 4 5 6 7 8 9 | initialize the QgsApplication
create the QgsServer
the main server loop waits forever for client requests:
for each incoming request:
create a QgsServerRequest request
create a QgsServerResponse response
call QgsServer.handleRequest(request, response)
filter plugins may be executed
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) :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | from qgis.core import QgsApplication
from qgis.server import *
app = QgsApplication([], False)
# Create the server instance, it may be a single one that
# is reused on multiple requests
server = QgsServer()
# Create the request by specifying the full URL and an optional body
# (for example for POST requests)
request = QgsBufferServerRequest(
'http://localhost:8081/?MAP=/qgis-server/projects/helloworld.qgs' +
'&SERVICE=WMS&REQUEST=GetCapabilities')
# Create a response objects
response = QgsBufferServerResponse()
# Handle the request
server.handleRequest(request, response)
print(response.headers())
print(response.body().data().decode('utf8'))
app.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/master/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 server plugins docs.
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 :
1 2 3 4 5 6 7 8 9 10 11 12 13 | for each incoming request:
create GET/POST request handler
pass request to an instance of QgsServerInterface
call requestReady filters
if there is not a response:
if SERVICE is WMS/WFS/WCS:
create WMS/WFS/WCS service
call service’s executeRequest
possibly call sendResponse for each chunk of bytes
sent to the client by a streaming services (WFS)
call responseComplete
call sendResponse
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 est normalement fait après que les services centraux aient terminé leur processus et après que le hook responseComplete ait été appelé, mais dans quelques cas, XML peut devenir si énorme qu’une implémentation XML de streaming était nécessaire (WFS GetFeature est l’un d’entre eux), dans ce cas, sendResponse
est appelé plusieurs fois avant que la réponse ne soit complète (et avant que responseComplete
ne soit appelé). La conséquence évidente est que sendResponse
est normalement appelé une fois mais peut exceptionnellement être appelé plusieurs fois et dans ce cas (et seulement dans ce cas) il est également appelé avant responseComplete
.
sendResponse
est le meilleur endroit pour manipuler directement la sortie du service de base et tandis que responseComplete
est généralement aussi une option, sendResponse
est la seule option viable en cas de services de streaming.
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
1 2 3 4 5 | PYTHON_PLUGINS_PATH/
HelloServer/
__init__.py --> *required*
HelloServer.py --> *required*
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 suivant met en œuvre un filtre minimal qui imprime HelloServer! dans le cas où le paramètre SERVICE est égal à « HELLO ».
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | class HelloFilter(QgsServerFilter):
def __init__(self, serverIface):
super().__init__(serverIface)
def requestReady(self):
QgsMessageLog.logMessage("HelloFilter.requestReady")
def sendResponse(self):
QgsMessageLog.logMessage("HelloFilter.sendResponse")
def responseComplete(self):
QgsMessageLog.logMessage("HelloFilter.responseComplete")
request = self.serverInterface().requestHandler()
params = request.parameterMap()
if params.get('SERVICE', '').upper() == 'HELLO':
request.clear()
request.setResponseHeader('Content-type', 'text/plain')
# Note that the content is of type "bytes"
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(), 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à :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | class ParamsFilter(QgsServerFilter):
def __init__(self, serverIface):
super(ParamsFilter, self).__init__(serverIface)
def requestReady(self):
request = self.serverInterface().requestHandler()
params = request.parameterMap( )
request.setParameter('TEST_NEW_PARAM', 'ParamsFilter')
def responseComplete(self):
request = self.serverInterface().requestHandler()
params = request.parameterMap( )
if params.get('TEST_NEW_PARAM') == 'ParamsFilter':
QgsMessageLog.logMessage("SUCCESS - ParamsFilter.responseComplete")
else:
QgsMessageLog.logMessage("FAIL - ParamsFilter.responseComplete")
|
Ceci est un extrait de ce que vous pouvez voir dans le fichier log:
1 2 3 4 5 6 | src/core/qgsmessagelog.cpp: 45: (logMessage) [0ms] 2014-12-12T12:39:29 plugin[0] HelloServerServer - loading filter ParamsFilter
src/core/qgsmessagelog.cpp: 45: (logMessage) [1ms] 2014-12-12T12:39:29 Server[0] Server plugin HelloServer loaded!
src/core/qgsmessagelog.cpp: 45: (logMessage) [0ms] 2014-12-12T12:39:29 Server[0] Server python plugins loaded
src/mapserver/qgshttprequesthandler.cpp: 547: (requestStringToParameterMap) [1ms] inserting pair SERVICE // HELLO into the parameter map
src/mapserver/qgsserverfilter.cpp: 42: (requestReady) [0ms] QgsServerFilter plugin default requestReady called
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 pouviez 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 pourriez ensuite injecter vos resultats 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | from qgis.server import *
from qgis.PyQt.QtCore import *
from qgis.PyQt.QtGui import *
class WatermarkFilter(QgsServerFilter):
def __init__(self, serverIface):
super().__init__(serverIface)
def responseComplete(self):
request = self.serverInterface().requestHandler()
params = request.parameterMap( )
# Do some checks
if (params.get('SERVICE').upper() == 'WMS' \
and params.get('REQUEST').upper() == 'GETMAP' \
and not request.exceptionRaised() ):
QgsMessageLog.logMessage("WatermarkFilter.responseComplete: image ready %s" % request.parameter("FORMAT"))
# Get the image
img = QImage()
img.loadFromData(request.body())
# Adds the watermark
watermark = QImage(os.path.join(os.path.dirname(__file__), 'media/watermark.png'))
p = QPainter(img)
p.drawImage(QRect( 20, 20, 40, 40), watermark)
p.end()
ba = QByteArray()
buffer = QBuffer(ba)
buffer.open(QIODevice.WriteOnly)
img.save(buffer, "PNG" if "png" in request.parameter("FORMAT") else "JPG")
# Set the body
request.clearBody()
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 entites 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 :
1 2 3 4 5 | PYTHON_PLUGINS_PATH/
MyAccessControl/
__init__.py --> *required*
AccessControl.py --> *required*
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¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | class AccessControlFilter(QgsAccessControlFilter):
def __init__(self, server_iface):
super().__init__(server_iface)
def layerFilterExpression(self, layer):
""" Return an additional expression filter """
return super().layerFilterExpression(layer)
def layerFilterSubsetString(self, layer):
""" Return an additional subset string (typically SQL) filter """
return super().layerFilterSubsetString(layer)
def layerPermissions(self, layer):
""" Return the layer rights """
return super().layerPermissions(layer)
def authorizedLayerAttributes(self, layer, attributes):
""" Return the authorised layer attributes """
return super().authorizedLayerAttributes(layer, attributes)
def allowToEdit(self, layer, feature):
""" Are we authorise to modify the following geometry """
return super().allowToEdit(layer, feature)
def cacheKey(self):
return super().cacheKey()
class AccessControlServer:
def __init__(self, serverIface):
""" Register AccessControlFilter """
serverIface.registerAccessControl(AccessControlFilter(self.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 entite .canUpdate
pour pouvoir mettre à jour une entité.canDelete
pour pouvoir supprimer une entite.
Exemple :
1 2 3 4 5 | def layerPermissions(self, layer):
rights = QgsAccessControlFilter.LayerPermissions()
rights.canRead = True
rights.canInsert = rights.canUpdate = rights.canDelete = False
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 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | from qgis.server import QgsService
from qgis.core import QgsMessageLog
class CustomServiceService(QgsService):
def __init__(self):
QgsService.__init__(self)
def name(self):
return "CUSTOM"
def version(self):
return "1.0.0"
def allowMethod(method):
return True
def executeRequest(self, request, response, project):
response.setStatusCode(200)
QgsMessageLog.logMessage('Custom service executeRequest')
response.write("Custom service executeRequest")
class CustomService():
def __init__(self, serverIface):
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 » :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 | import json
import os
from qgis.PyQt.QtCore import QBuffer, QIODevice, QTextStream, QRegularExpression
from qgis.server import (
QgsServiceRegistry,
QgsService,
QgsServerFilter,
QgsServerOgcApi,
QgsServerQueryStringParameter,
QgsServerOgcApiHandler,
)
from qgis.core import (
QgsMessageLog,
QgsJsonExporter,
QgsCircle,
QgsFeature,
QgsPoint,
QgsGeometry,
)
class CustomApiHandler(QgsServerOgcApiHandler):
def __init__(self):
super(CustomApiHandler, self).__init__()
self.setContentTypes([QgsServerOgcApi.HTML, QgsServerOgcApi.JSON])
def path(self):
return QRegularExpression("/customapi")
def operationId(self):
return "CustomApiXYCircle"
def summary(self):
return "Creates a circle around a point"
def description(self):
return "Creates a circle around a point"
def linkTitle(self):
return "Custom Api XY Circle"
def linkType(self):
return QgsServerOgcApi.data
def handleRequest(self, context):
"""Simple Circle"""
values = self.values(context)
x = values['x']
y = values['y']
r = values['r']
f = QgsFeature()
f.setAttributes([x, y, r])
f.setGeometry(QgsCircle(QgsPoint(x, y), r).toCircularString())
exporter = QgsJsonExporter()
self.write(json.loads(exporter.exportFeature(f)), context)
def templatePath(self, context):
# The template path is used to serve HTML content
return os.path.join(os.path.dirname(__file__), 'circle.html')
def parameters(self, context):
return [QgsServerQueryStringParameter('x', True, QgsServerQueryStringParameter.Type.Double, 'X coordinate'),
QgsServerQueryStringParameter(
'y', True, QgsServerQueryStringParameter.Type.Double, 'Y coordinate'),
QgsServerQueryStringParameter('r', True, QgsServerQueryStringParameter.Type.Double, 'radius')]
class CustomApi():
def __init__(self, serverIface):
api = QgsServerOgcApi(serverIface, '/customapi',
'custom api', 'a custom api', '1.1')
handler = CustomApiHandler()
api.registerHandler(handler)
serverIface.serviceRegistry().registerApi(api)
|