20. QGIS Server en Python
20.1. Introductie
Lees de QGIS Server Gebruikershandleiding om meer te leren over QGIS Server.
QGIS Server is drie verschillende dingen:
QGIS Server bibliotheek: een bibliotheek die een API verschaft voor het maken van webservices voor OGC
QGIS Server FCGI: een FCGI binaire toepassing
qgis_mapserv.fcgi
die, samen met een webserver, een set services van OGC implementeert (WMS, WFS, WCS etc.) en API’s voor OGC (WFS3/OAPIF)QGIS Development Server: een binaire toepassing voor een ontwikkelingsserver
qgis_mapserver
een set services van OGC implementeert (WMS, WFS, WCS etc.) en API’s voor OGC (WFS3/OAPIF)
Dit hoofdstuk van het kookboek focust op het eerste onderwerp en door het gebruik van de API van QGIS Server uit te leggen, lat het zien hoe het mogelijk is Python te gebruiken om het gedrag van de server uit te breiden, te verbeteren of aan te passen of hoe de API van QGIS Server te gebruiken om QGIS server in een andere toepassing in te bedden.
Er zijn een aantal verschillende manieren waarmee u het gedrag van QGIS Server kunt wijzigen of de mogelijkheden ervan kan uitbreiden om nieuwe aangepaste services of API’s aan te bieden. Dit zijn de belangrijkste scenario’s die u zou kunnen tegenkomen:
INBEDDEN → API van QGIS Server gebruiken vanuit een andere toepassing
ZELFSTANDIG → QGIS Server uitvoeren als een zelfstandige service voor WSGI/HTTP
FILTERS → QGIS Server verbeteren/aanpassen met plug-ins voor filters
SERVICES → Een nieuwe SERVICE toevoegen
OGC APIs → Een nieuwe OGC API toevoegen
Ingebedde en zelfstandige toepassingen vereisen het gebruiken van de Python API voor QGIS Server direct vanuit een ander script of toepassing voor Python. De resterende opties zijn beter geschikt voor wanneer u aangepaste mogelijkheden wilt toevoegen aan een standaard QGIS Server binaire toepassing (FCGI of ontwikkelingsserver): in dit geval dient u een plug-in voor Python voor de toepassing van de server te schrijven en uw aangepaste filters, services of API’s te registreren.
20.2. Basisbeginselen Server API
De betrokken fundamentele klassen voor een typische toepassing van QGIS Server zijn:
QgsServer
de server instance (gewoonlijk één enkele instance voor de gehele levenscyclus van de toepassing)QgsServerRequest
het object request object (gewoonlijk gemaakt voor elk verzoek)QgsServer.handleRequest(request, response)
verwerkt het verzoek en vult het antwoord
De werkstroom voor een QGIS Server FCGI of ontwikkelingsserver kan als volgt worden samengevat:
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
Binnen de methode QgsServer.handleRequest(request, response)
worden de callbacks voor de filterplug-ins aangeroepen en QgsServerRequest
en QgsServerResponse
worden voor de plug-ins beschikbaar gemaakt via de klasse QgsServerInterface
.
Waarschuwing
Klassen voor QGIS server zijn niet thread safe, u zou altijd een model voor multiverwerking of containers moeten gebruiken bij het bouwen van schaalbare toepassingen, gebaseerd op de API van QGIS Server.
20.3. Zelfstandig of inbedden
Voor zelfstandige toepassingen voor de server of inbedden dient u de hierboven vermelde klassen voor de server direct te gebruiken, verpak ze in een implementatie voor een webserver die alle interacties met de cliënt voor het protocol HTTP beheert.
Een minimaal voorbeeld voor het gebruiken van de API voor QGIS Server API (zonde het gedeelte voor HTTP) volgt:
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()
Hier staat een volledig voorbeeld van een zelfstandige toepassing, ontwikkeld voor het doorlopend testen van integraties in de opslagplaats voor de broncode van QGIS. Het laat een brede set van verschillende plug-ins voor filters en authenticatieschema’s zien (niet bedoeld voor productie, omdat zij alleen voor testdoeleinden werden ontwikkeld, maar nog steeds interessant zijn om van te leren): qgis_wrapped_server.py
20.4. Server plug-ins
Server Python plug-ins worden geladen als de toepassing QGIS Server start en kunnen worden gebruikt om filters, services of API’s te registreren.
De structuur van een server plug-in lijkt zeer veel op zijn collega voor de desktop, een object QgsServerInterface
wordt beschikbaar gemaakt voor de plug-ins en de plug-ins kunnen één of meer aangepaste filters, services of API’s registreren in het overeenkomende register door een van de methoden te gebruiken die worden blootgelegd door de interface van de server.
20.4.1. Server filter plug-ins
Filters zijn er in drie verschillende smaken en zij kunnen worden geïnstantieerd door een van de klassen hieronder te subklasseren en door de overeenkomende methode van QgsServerInterface
aan te roepen:
Type filter |
Basisklasse |
registratie QgsServerInterface |
I/O |
||
Access Control |
||
Cache |
20.4.1.1. I/O-filters
I/O-filters kunnen de in- en uitvoer van de server (het verzoek en het antwoord) van de bronservices (WMS, WFS etc.) aanpassen, wat het mogelijk maakt de werkstroom van de services te bewerken. Het is bijvoorbeeld mogelijk toegang tot de geselecteerde lagen te beperken, een XSL-stijlblad te injecteren in het antwoord van XML, om een watermerk toe te voegen aan een gemaakte afbeelding voor WMS, enzovoort.
Vanaf dit punt zou u het misschien nuttig kunnen vinden om eens snel te kijken naar de documentatie voor API server plug-ins.
Elk filter zou ten minste één van drie instructies moeten implementeren:
Alle filters hebben toegang tot het object voor het verzoek/antwoord (QgsRequestHandler
) en kan al zijn eigenschappen bewerken (invoer/uitvoer) en exceptions opwerpen (hoewel op een bijzondere manier zoals we hieronder zullen zien).
Al deze methoden geven een Booleaanse waarde terug die aangeeft of de aanroep zou moeten worden doorgezet naar de opeenvolgende filters. Als één van deze methoden False
teruggeeft stopt de keten, anders zal de aanroep worden doorgezet naar het volgende filter.
Hier is de pseudocode die weergeeft hoe de server een gewoon verzoek afhandelt en wanneer de callbacks van het filter worden aangeroepen:
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
De volgende alinea’s beschrijven de beschikbare terugkoppelingen tot in detail.
20.4.1.1.1. onRequestReady
Dit wordt aangeroepen als het verzoek gereed is: inkomende URL en gegevens zijn geparset en vóór te schakelen naar de bronservices (WMS, WFS etc.), is dit het punt waar u de invoer kunt bewerken en acties kunt uitvoeren als:
authenticatie/autorisatie
doorverwijzingen
bepaalde parameters toevoegen/verwijderen (typenamen bijvoorbeeld)
exceptions opwerpen
U zou zelfs een bronservice volledig kunnen vervangen door de parameter SERVICE te wijzigen en op die manier de bronservice volledig omzeilen (niet dat dat echter enige zin zou hebben).
20.4.1.1.2. onSendResponse
Dit wordt aangeroepen wanneer een gedeeltelijk antwoord wordt verwijderd uit de buffer voor het antwoord (d.i. naar FCGI stdout
als de fcgi server wordt gebruikt) en vanaf daar naar de cliënt. Dit gebeurt als heel veel inhoud wordt gestroomd (zoals WFS GetFeature). In dat geval zou onSendResponse()
meerdere keren kunnen worden aangeropen.
Onthoud dat als het antwoord niet wordt gestroomd, dan zal onSendResponse()
helemaal niet worden aangeroepen.
In alle gevallen zal het laatste (of unieke) gedeelte worden verzonden aan de cliënt na het aanroepen van onResponseComplete()
.
Teruggeven van False
zal het doorsturen van gegevens naar de cliënt voorkomen. Dit is gewenst als een plug-in alle gedeelten uit een antwoord wil verzamelen en het antwoord onderzoeken of wijzigen in onResponseComplete()
.
20.4.1.1.3. onResponseComplete
Dit wordt eenmaal aangeroepen wanneer de bronservices (indien aangesproken) hun proces voltooien en het verzoek gereed is om te worden verzonden naar de cliënt. Zoals hierboven besproken zal deze methode worden aangeroepen vóórdat het laatste (of unieke) gedeelte gegevens is verzonden aan de cliënt. Voor services voor streaming zouden meerdere aanroepen naar onSendResponse()
gebeurd kunnen zijn.
onResponseComplete()
is de ideale plek om implementatie voor nieuwe services te verschaffen (WPS of aangepaste services) en om de uitvoer, komende vanaf bronservices, direct te bewerken (bijvoorbeeld om een watermerk aan een afbeelding van WMS toe te voegen).
Onthoud dat het teruggeven van False
zal voorkomen dat de volgende plug-ins onResponseComplete()
uitvoeren maar, in elk geval, voorkomen dat het antwoord wordt verzonden aan de cliënt.
20.4.1.1.4. Uitzonderingen opwerpen vanuit een plug-in
Er moet nog steeds wat werk worden gedaan aan dit onderwerp: de huidige implementatie kan onderscheid maken tussen afgehandelde en niet afgehandelde uitzonderingen door een eigenschap QgsRequestHandler
in te stellen voor een instance van QgsMapServiceException. Op deze manier kan de belangrijkste code in C++ afgehandelde uitzonderingen voor Python opvangen en niet afgehandelde uitzonderingen negeren (of beter nog: ze loggen).
Deze benadering werkt in de basis maar is nog niet erg “Pythonisch”: een betere benadering zou zijn om uitzonderingen op te werpen vanuit de code van Python en ze op zien borrelen in een lus van C++ om daar te worden afgehandeld.
20.4.1.1.5. Een plug-in voor de server schrijven
Een plug-in voor de server is een standaard plug-in in Python voor QGIS Python zoals beschreven in Python plug-ins ontwikkelen, dat eenvoudigweg een aanvullende (of alternatieve) interface verschaft: een typische plug-in voor QGIS Desktop heeft toegang tot de toepassing QGIS via de instantie QgisInterface
, een plug-in voor de server heeft alleen toegang tot een QgsServerInterface
wanneer het wordt uitgevoerd binnen de context van de toepassing voor de QGIS Server.
Een speciaal item voor metadata is nodig (in metadata.txt
) om QGIS Server te vertellen dat een plug-in een interface voor de server heeft:
server=True
Belangrijk
Alleen plug-ins die de metadata server=True
hebben ingesteld zullen worden geladen en uitgevoerd door QGIS Server.
De voorbeeldplug-in qgis3-server-vagrant die hier wordt besproken (naast vele andere) is beschikbaar op GitHub, een klein aantal serverplug-ins zijn ook gepubliceerd in de officiële opslagplaats van plug-ins voor QGIS.
20.4.1.1.5.1. Plug-inbestanden
Hier is de mappenstructuur van onze voorbeeld-plug-in voor de server.
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
Dit bestand wordt vereist door het systeem voor importeren van Python. Ook vereist QGIS Server dat dit bestand een functie classFactory()
bevat, die wordt aangeroepen als de plug-in wordt geladen in QGIS Server. Het ontvangt een verwijzing naar de instantie van QgsServerInterface
en moet een instantie teruggeven van de klasse van uw plug-in. Dit is hoe de voorbeeldplug-in __init__.py
eruitziet:
def serverClassFactory(serverIface):
from .HelloServer import HelloServerServer
return HelloServerServer(serverIface)
20.4.1.1.5.1.2. HelloServer.py
Dit is waar de magie gebeurt en dit is hoe de magie eruit ziet: (bijv. HelloServer.py
)
Een plug-in voor de server bestaat gewoonlijk uit één of meer callbacks, verpakt in instanties van een QgsServerFilter
.
Elk QgsServerFilter
implementeert één of meer van de volgende callbacks:
Het volgende voorbeeld implementeert een minimaal filter dat HelloServer! afdrukt in het geval dat de parameter SERVICE gelijk is aan “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
De filters moeten worden geregistreerd in de serverIface zoals in het volgende voorbeeld:
class HelloServerServer:
def __init__(self, serverIface):
serverIface.registerFilter(HelloFilter(serverIface), 100)
De tweede parameter van registerFilter()
stelt een prioriteit in die de volgorde definieert voor de callbacks met dezelfde naam (de laagste prioriteit wordt het eerst uitgevoerd).
Door de drie callbacks te gebruiken, kunnen plug-ins de invoer en/of de uitvoer van de server op veel verschillende manieren manipuleren. Op elk moment heeft de instantie van de plug-in toegang tot de QgsRequestHandler
via de QgsServerInterface
, de QgsRequestHandler
heeft veel methoden die kunnen worden gebruikt om de parameters voor de invoer te wijzigen vóór de bronverwerking door de server (door requestReady()
te gebruiken) of nadat het verzoek is verwerkt door de bronservices (door sendResponse()
te gebruiken).
De volgende voorbeelden behandelen enkele veel voorkomende gevallen van gebruik:
20.4.1.1.5.2. De invoer aanpassen
De voorbeeld plug-in bevat een testvoorbeeld dat parameters voor invoer wijzigt die afkomstig zijn uit de tekenreeks van de query, in dit voorbeeld wordt een nieuwe parameter ingevoerd in de (reeds geparste) parameterMap
, deze parameter is dan zichtbaar voor bronservices (WMS etc.), aan het einde van de verwerking door bronservices controleren we of de parameter er nog steeds is:
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
Dit is een extract van wat u ziet in het logbestand:
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
Op de geaccentueerde regel geeft de tekenreeks “SUCCESS” aan dat de plug-in voor de test is geslaagd.
Dezelfde techniek kan worden gebruikt om een aangepaste service te gebruiken in plaats van een bronservice: u zou bijvoorbeeld een verzoek WFS SERVICE kunnen overslaan of elk ander bronverzoek door slechts de parameter SERVICE naar iets anders te wijzigen en de bronservice zal worden overgeslagen. Dan kunt u uw aangepaste resultaten invoeren in de uitvoer en die naar de cliënt verzenden (dat is hieronder uitgelegd).
Tip
Als u echt een aangepaste service wilt implementeren wordt aanbevolen om QgsService
te subklasseren en uw service te registreren in registerFilter()
door de registerService(service)
daarvan aan te roepen
20.4.1.1.5.3. De uitvoer aanpassen of vervangen
Het voorbeeld watermark filter laat zien hoe de uitvoer van WMS te vervangen door een nieuwe afbeelding die wordt verkregen door het toevoegen van een afbeelding van een watermerk bovenop de afbeelding van WMS die werd gegenereerd door de bronservice van 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
In dit voorbeeld is de waarde van de parameter SERVICE gecontroleerd en als het inkomende verzoek een WMS GETMAP is en er geen uitzonderingen zijn ingesteld door een eerder uitgevoerde plug-in of door de bronservice (WMS in dit geval), wordt de door WMS gegenereerde afbeelding opgehaald uit de buffer voor de uitvoer en wordt de afbeelding van het watermerk toegevoegd. De laatste stap is om de buffer voor de uitvoer op te schonen en die te vervangen door de nieuw gegenereerde afbeelding. Onthoud dat, in een situatie in de echte wereld, we ook het type van de verzochte afbeelding zouden controleren in plaats van alleen PNG of JPG te ondersteunen.
20.4.1.2. Filters voor Access control
Filters voor Access control geven de ontwikkelaar fijnmazig beheer over tot welke lagen, objecten en attributen toegang kan worden verkregen, de volgende callbacks kunnen worden geïmplementeerd in een filter voor Access control:
20.4.1.2.1. Plug-inbestanden
Hier is de mappenstructuur van onze voorbeeld-plug-in:
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
Dit bestand wordt vereist door het systeem voor importeren van Python. Net als voor alle plug-ins voor QGIS Server bevat dit bestand een functie classFactory()
bevat, die wordt aangeroepen als de plug-in wordt geladen in QGIS Server bij het opstarten. Het ontvangt een verwijzing naar een instantie van QgsServerInterface
en moet een instantie teruggeven van de klasse van uw plug-in. Dit is hoe de voorbeeldplug-in __init__.py
er uit ziet:
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)
Dit voorbeeld geeft een voorbeeld voor volledige toegang voor iedereen.
Het is de rol van de plug-in om te weten wie er is ingelogd.
Voor al deze methoden hebben de laag als argument om in staat te zien om de rechten per laag aan te passen.
20.4.1.2.2. layerFilterExpression
Gebruikt om een expressie toe te voegen om de resultaten te beperken.
Bijvoorbeeld om te beperken tot de mogelijkheid waar het attribuut role
gelijk is aan user
.
def layerFilterExpression(self, layer):
return "$role = 'user'"
20.4.1.2.3. layerFilterSubsetString
Hetzelfde als hiervoor maar dan door de SubsetString
te gebruiken (uitgevoerd in de database)
Bijvoorbeeld om te beperken tot de mogelijkheid waar het attribuut role
gelijk is aan user
.
def layerFilterSubsetString(self, layer):
return "role = 'user'"
20.4.1.2.4. layerPermissions
Toegang beperken tot de laag.
Geef een object terug van het type LayerPermissions()
, die de eigenschappen heeft:
canRead
om het te zien in deGetCapabilities
en rechten voor lezen hebben.canInsert
om een nieuw object te kunnen invoegen.canUpdate
om een object te kunnen bijwerken.canDelete
om een object te kunnen verwijderen.
Bijvoorbeeld om alles te beperken tot toegang voor Alleen-lezen:
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
Dit wordt gebruikt om het bewerken van een subset van objecten te beperken.
Het wordt gebruikt in het protocol WFS-Transaction
.
Bijvoorbeeld om het mogelijk te maken alleen objecten te bewerken die het attribuut role
hebben met de waarde user
.
def allowToEdit(self, layer, feature):
return feature.attribute('role') == 'user'
20.4.1.2.7. cacheKey
QGIS Server onderhoudt een cache van de capabilities, om dan een cache per rol te hebben kunt u de rol teruggeven met deze methode. Of geef None
terug om de cache volledig uit te schakelen.
20.4.2. Aangepaste services
In QGIS Server zijn bronservices, zoals WMS, WFS en WCS, geïmplementeerd als subklassen van QgsService
.
U kunt, om een nieuwe service te implementeren die zal worden uitgevoerd als de tekenreeks voor de query voor de parameter SERVICE
overeenkomt met de naam voor de service, uw eigen QgsService
implementeren en uw service registreren in serviceRegistry()
door de methode registerService(service)
daarvan aan te roepen
Hier is een voorbeeld van een aangepaste service, genaamd 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. Aangepaste API’s
In QGIS Server worden bron-API’S van OGC, zoals OAPIF, (alias WFS3) geïmplementeerd als collecties van subklassen van QgsServerOgcApiHandler
, die zijn geregistreerd bij een instantie van QgsServerOgcApi
(of zijn ouderklasse QgsServerApi
).
U kunt, om een nieuwe API te implementeren die zal worden uitgevoerd als het pad voor de URL overeenkomt met een bepaalde URL, uw eigen instanties QgsServerOgcApiHandler
implementeren, ze toevoegen aan een QgsServerOgcApi
en de API registreren in het serviceRegistry()
door de methode registerApi(api)
daarvan aan te roepen.
Hier is een voorbeeld van een aangepaste API die zal worden uitgevoerd als de URL /customapi
bevat:
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)