20. QGIS Server e Python
20.1. Introduzione
Per saperne di più su QGIS Server, leggere il QGIS Server Guida/Manuale.
QGIS Server è tre cose diverse:
Libreria QGIS Server: una libreria che fornisce un’API per la creazione di servizi web OGC.
QGIS Server FCGI: un’applicazione binaria FCGI
qgis_mapserv.fcgi
che, insieme a un server web, implementa una serie di servizi OGC (WMS, WFS, WCS ecc.) e API OGC (WFS3/OAPIF).QGIS Development Server: un’applicazione binaria del server di sviluppo
qgis_mapserver
che implementa una serie di servizi OGC (WMS, WFS, WCS ecc.) e di API OGC (WFS3/OAPIF).
Questo capitolo del manuale si concentra sul primo argomento e, spiegando l’uso dell’API del server QGIS, mostra come sia possibile usare Python per estendere, migliorare o personalizzare il comportamento del server o come usare l’API del server QGIS per incorporare il server QGIS in un’altra applicazione.
Esistono diversi modi con cui puoi alterare il comportamento di QGIS Server o per estendere le sue capabilities per offrire nuovi servizi personalizzati o API:
EMBEDDING → Usare l’API del server QGIS da un’altra applicazione Python
STANDALONE → Eseguire QGIS Server come servizio WSGI/HTTP standalone
FILTRI → Migliorare/Personalizzare QGIS Server con i plugin di filtro
SERVICES → Aggiungere un nuovo SERVICE
API OGC → Aggiungere una nuova OGC API
Le applicazioni embedding e standalone richiedono l’utilizzo dell’API Python di QGIS Server direttamente da un altro script o applicazione Python. Le altre opzioni sono più adatte quando vuoi aggiungere funzionalità personalizzate a un’applicazione binaria standard di QGIS Server (FCGI o server di sviluppo): in questo caso dovrai scrivere un plugin Python per l’applicazione server e registrare i tuoi filtri, servizi o API personalizzati.
20.2. Nozioni di base server API
Le classi fondamentali coinvolte in una tipica applicazione di QGIS Server sono:
QgsServer
l’istanza del server (in genere una singola istanza per tutta la durata dell’applicazione)QgsServerRequest
l’oggetto richiesta (tipicamente ricreato ad ogni richiesta)QgsServer.handleRequest(request, response)
elabora la richiesta e popola la risposta.
Il flusso di lavoro di QGIS Server FCGI o server di sviluppo può essere riassunto come segue:
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
All’interno del metodo QgsServer.handleRequest(request, response)
vengono richiamate le callback dei plugin di filtro e QgsServerRequest
e QgsServerResponse
sono resi disponibili ai plugin attraverso la classe QgsServerInterface
.
Avvertimento
Le classi del server QGIS non sono thread safe; per la creazione di applicazioni scalabili basate sull’API del server QGIS, devi utilizzare sempre un modello multiprocesso o dei container.
20.3. Standalone o embedding
Per le applicazioni server standalone o incorporate, è necessario utilizzare direttamente le classi server di cui sopra, impacchettandole in un’implementazione del server web che gestisce tutte le interazioni del protocollo HTTP con il client.
Segue un esempio minimo di utilizzo dell’API di QGIS Server (senza la parte 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()
Ecco un esempio completo di applicazione standalone sviluppata per il test delle integrazioni continue sul repository del codice sorgente di QGIS, che mette in mostra un’ampia serie di diversi filtri per i plugin e schemi di autenticazione (non sono adatti alla produzione perché sono stati sviluppati solo a scopo di test, ma sono comunque interessanti per l’apprendimento): qgis_wrapped_server.py.
20.4. Plugin del server
I plugin python del server vengono caricati al momento dell’avvio dell’applicazione QGIS Server e possono essere utilizzati per registrare filtri, servizi o API.
La struttura di un plugin server è molto simile alla sua omologa desktop, un oggetto QgsServerInterface
è reso disponibile ai plugin e questi ultimi possono registrare uno o più filtri, servizi o API personalizzati nel registro corrispondente, utilizzando uno dei metodi esposti dall’interfaccia server.
20.4.1. Plugin filtro server
I filtri sono disponibili in tre varianti e possono essere istanziati sottoclassando una delle classi seguenti e richiamando il metodo corrispondente di QgsServerInterface
:
Tipo filtro |
Classe base |
Registrazione QgsServerInterface |
I/O |
||
Controllo accessi |
||
Cache |
20.4.1.1. Filtri I/O
I filtri I/O possono modificare l’input e l’output del server (la richiesta e la risposta) dei servizi principali (WMS, WFS ecc.), consentendo di effettuare qualsiasi tipo di manipolazione del flusso di lavoro dei servizi. È possibile, ad esempio, limitare l’accesso a layer selezionati, inserire un foglio di stile XSL nella risposta XML, aggiungere un watermark a un’immagine WMS generata e così via.
A questo punto, ti può essere utile una rapida occhiata ai server plugins API docs.
Ogni filtro deve implementare almeno uno dei tre callback:
Tutti i filtri hanno accesso all’oggetto request/response (QgsRequestHandler
) e possono manipolare tutte le sue proprietà (input/output) e sollevare eccezioni (anche se in modo piuttosto particolare, come vedremo più avanti).
Tutti questi metodi restituiscono un valore booleano che indica se la call deve essere propagata ai filtri successivi. Se uno di questi metodi restituisce False
, la catena si ferma, altrimenti la call si propagherà al filtro successivo.
Ecco lo pseudo-codice che mostra come il server gestisce una richiesta tipica e quando vengono richiamati i callback del filtro:
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
I paragrafi seguenti descrivono in dettaglio i callback disponibili.
20.4.1.1.1. onRequestReady
Viene chiamato quando la richiesta è pronta: l’URL e i dati in entrata sono stati analizzati e prima di entrare nei servizi principali (WMS, WFS ecc.) si passa a questo punto in cui puoi manipolare l’input ed eseguire azioni come:
autenticazione/autorizzazione
redirects
aggiungere/rimuovere alcuni parametri (ad esempio i nomi dei tipi)
sollevare eccezioni
Potresti anche sostituire completamente un servizio principale cambiando il parametro SERVICE e quindi bypassando completamente il servizio principale (non che questo abbia molto senso, però).
20.4.1.1.2. onSendResponse
Viene richiamato ogni volta che un output parziale viene scaricato dal buffer di risposta (cioè a FCGI stdout
se si usa il server fcgi) e da lì al client. Questo accade quando vengono trasmessi contenuti enormi (come WFS GetFeature). In questo caso onSendResponse()
può essere chiamato più volte.
Da notare che se la risposta non è in streaming, onSendResponse()
non sarà chiamato affatto.
In ogni caso, l’ultimo (o unico) blocco sarà inviato al client dopo una chiamata a onResponseComplete()
.
Il ritorno di False
impedisce il trasferimento dei dati al client. Questo è auspicabile quando un plugin vuole raccogliere tutti i blocchi di una risposta ed esaminare o modificare la risposta in onResponseComplete()
.
20.4.1.1.3. onResponseComplete
Questo metodo viene richiamato una volta quando i servizi di base (se sono attivi) terminano il loro processo e la richiesta è pronta per essere inviata al client. Come discusso in precedenza, questo metodo sarà chiamato prima che l’ultimo (o unico) blocco di dati sia inviato al client. Per i servizi di streaming, potrebbero essere state chiamate più volte onSendResponse()
.
onResponseComplete()
è il luogo ideale per fornire l’implementazione di nuovi servizi (WPS o servizi personalizzati) e per eseguire la manipolazione diretta dell’output proveniente dai servizi principali (ad esempio per aggiungere una filigrana a un’immagine WMS).
Da notare che la restituzione di False
impedirà ai plugin successivi di eseguire onResponseComplete()
ma, in ogni caso, impedirà l’invio della risposta al client.
20.4.1.1.4. Generazione di eccezioni da parte di un plugin
C’è ancora del lavoro da fare su questo argomento: l’implementazione attuale può distinguere tra eccezioni gestite e non gestite impostando una proprietà QgsRequestHandler
a un’istanza di QgsMapServiceException, in questo modo il codice principale C++ può catturare le eccezioni python gestite e ignorare quelle non gestite (o meglio: registrarle).
Questo approccio fondamentalmente funziona, ma non è molto «pythonic»: un approccio migliore sarebbe quello di sollevare eccezioni dal codice python e vederle confluire nel ciclo C++ per essere gestite lì.
20.4.1.1.5. Scrivere un server plugin
Un plugin server è un plugin Python standard di QGIS, come descritto in Sviluppo di plugin Python, che fornisce solo un’interfaccia aggiuntiva (o alternativa): un tipico plugin desktop di QGIS ha accesso all’applicazione QGIS attraverso l’istanza QgisInterface
, un plugin server ha accesso solo a una QgsServerInterface
quando viene eseguito nel contesto dell’applicazione QGIS Server.
Per far sì che QGIS Server sappia che un plugin ha un’interfaccia server, è necessaria una voce di metadati speciale (in metadata.txt
):
server=True
Importante
Solo i plugin con i metadati server=True
saranno caricati ed eseguiti da QGIS Server.
Il plugin di esempio qgis3-server-vagrant discusso qui (con molti altri) è disponibile su github, alcuni plugin per server sono anche pubblicati nel repository ufficiale dei QGIS plugins.
20.4.1.1.5.1. Plugin file
Ecco la struttura delle cartelle del nostro plugin server di esempio.
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
Questo file è richiesto dal sistema di importazione di Python. Inoltre, QGIS Server richiede che questo file contenga una funzione serverClassFactory()
, che viene chiamata quando il plugin viene caricato in QGIS Server all’avvio del server. Riceve un riferimento all’istanza di QgsServerInterface
e deve restituire l’istanza della classe del plugin. Ecco come appare il plugin di esempio __init__.py
:
def serverClassFactory(serverIface):
from .HelloServer import HelloServerServer
return HelloServerServer(serverIface)
20.4.1.1.5.1.2. HelloServer.py
È qui che avviene la magia e questo è l’aspetto della magia: (ad esempio HelloServer.py
)
Un plugin server consiste tipicamente in uno o più callback racchiusi in istanze di una QgsServerFilter
.
Ogni QgsServerFilter
implementa uno o più dei seguenti callback:
L’esempio seguente implementa un filtro minimo che stampa HelloServer! nel caso in cui il parametro SERVICE sia uguale a «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
I filtri devono essere registrati nella serverIface come nell’esempio seguente:
class HelloServerServer:
def __init__(self, serverIface):
serverIface.registerFilter(HelloFilter(serverIface), 100)
Il secondo parametro di registerFilter()
imposta una priorità che definisce l’ordine delle callback con lo stesso nome (la priorità più bassa viene invocata per prima).
Utilizzando i tre callback, i plugin possono manipolare l’input e/o l’output del server in molti modi diversi. In ogni momento, l’istanza del plugin ha accesso alla QgsRequestHandler
attraverso la QgsServerInterface
. La classe QgsRequestHandler
ha molti metodi che possono essere usati per modificare i parametri di ingresso prima di entrare nel nucleo di elaborazione del server (usando requestReady()
) o dopo che la richiesta è stata elaborata dai servizi centrali (usando sendResponse()
).
I seguenti esempi coprono alcuni casi d’uso comuni:
20.4.1.1.5.2. Modificare l’input
Il plugin di esempio contiene un esempio di test che modifica i parametri di input provenienti dalla stringa di query; in questo esempio un nuovo parametro viene inserito nella parameterMap
(già analizzata), questo parametro è poi visibile dai servizi di base (WMS, ecc.); alla fine dell’elaborazione dei servizi di base controlliamo che il parametro sia ancora presente:
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
Questo è un estratto di ciò che vedi nel file di 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
Nella riga evidenziata, la stringa «SUCCESS» indica che il plugin ha superato il test.
La stessa tecnica può essere sfruttata per utilizzare un servizio personalizzato al posto di quello principale: puoi ad esempio ignorare una richiesta WFS SERVICE o qualsiasi altra richiesta principale semplicemente cambiando il parametro SERVICE in qualcosa di diverso e il servizio principale verrà ignorato. Puoi quindi inserire i tuoi risultati personalizzati nell’output e inviarli al client (questo è spiegato più avanti).
Suggerimento
Se vuoi davvero implementare un servizio personalizzato, si raccomanda di sottoclassare QgsService
e di registrare il tuo servizio su registerFilter()
chiamando il suo registerService(service)
.
20.4.1.1.5.3. Modifica o sostituzione del risultato
L’esempio del filtro watermark mostra come sostituire l’output WMS con una nuova immagine ottenuta aggiungendo un’immagine watermark all’immagine WMS generata dal servizio centrale 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 questo esempio viene controllato il valore del parametro SERVICE e se la richiesta in arrivo è un WMS GETMAP e non sono state impostate eccezioni da un plugin precedentemente eseguito o dal servizio principale (WMS in questo caso), l’immagine generata da WMS viene recuperata dal buffer di output e viene aggiunta l’immagine del watermark. Il passo finale consiste nel cancellare il buffer di output e sostituirlo con la nuova immagine generata. Si noti che in una situazione reale si dovrebbe verificare anche il tipo di immagine richiesta, invece di supportare solo PNG o JPG.
20.4.1.2. Filtri controllo accesso
I filtri di controllo degli accessi danno allo sviluppatore un controllo a grana fine sui layer, sugli elementi e sugli attributi a cui si può accedere; le seguenti callback possono essere implementate in un filtro di controllo degli accessi:
20.4.1.2.1. Plugin file
Ecco la struttura dello cartella del nostro plugin di esempio:
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
Questo file è richiesto dal sistema di importazione di Python. Come per tutti i plugin del server QGIS, questo file contiene una funzione serverClassFactory()
, che viene chiamata quando il plugin viene caricato in QGIS Server all’avvio. Riceve un riferimento a un’istanza di QgsServerInterface
e deve restituire un’istanza della classe del plugin. Ecco come appare il plugin di esempio __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)
Questo esempio offre un accesso completo a tutti.
Il ruolo del plugin è quello di sapere chi è connesso.
In tutti questi metodi abbiamo il parametro layer on per poter personalizzare la restrizione per layer.
20.4.1.2.2. layerFilterExpression
Si usa per aggiungere una Espressione per limitare i risultati.
Ad esempio, per limitare gli elementi in cui l’attributo ``role”” è uguale a ``user””.
def layerFilterExpression(self, layer):
return "$role = 'user'"
20.4.1.2.3. layerFilterSubsetString
Come il precedente, ma utilizzando la SubsetString
(eseguita nel database).
Ad esempio, per limitare gli elementi in cui l’attributo ``role”” è uguale a ``user””.
def layerFilterSubsetString(self, layer):
return "role = 'user'"
20.4.1.2.4. layerPermissions
Limitare l’accesso al layer.
Restituisce un oggetto di tipo LayerPermissions()
, che ha le proprietà:
canRead
per vederlo inGetCapabilities
e avere accesso in lettura.canInsert
per poter inserire un nuovo elemento.canUpdate
per poter aggiornare un elemento.canDelete
per poter eliminare un elemento.
Ad esempio, per limitare l’accesso in sola lettura:
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
Viene utilizzato per limitare la modifica di un sottoinsieme di elementi.
È utilizzato nel protocollo WFS-Transaction
.
Ad esempio, per poter modificare solo gli elementi che hanno l’attributo role`
con il valore user
:
def allowToEdit(self, layer, feature):
return feature.attribute('role') == 'user'
20.4.1.2.7. cacheKey
QGIS Server mantiene una cache delle caratteristiche, quindi per avere una cache per role si può indicare role in questo metodo. Oppure restituire None
per disabilitare completamente la cache.
20.4.2. Servizi personalizzati
In QGIS Server, i servizi principali come WMS, WFS e WCS sono implementati come sottoclassi di QgsService
.
Per implementare un nuovo servizio che verrà eseguito quando il parametro della query string SERVICE
corrisponde al nome del servizio, puoi implementare la tua QgsService
e registrare il tuo servizio nel serviceRegistry()
chiamando il suo registerService(service)
.
Ecco un esempio di servizio personalizzato chiamato 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 personalizzate
In QGIS Server, le API OGC di base come OAPIF (alias WFS3) sono implementate come collezioni di QgsServerOgcApiHandler
che sono registrate in un’istanza di QgsServerOgcApi
(o della sua classe madre QgsServerApi
).
Per implementare una nuova API che verrà eseguita quando il percorso dell’url corrisponde a un determinato URL, puoi implementare le tue istanze QgsServerOgcApiHandler
, aggiungerle a una QgsServerOgcApi
e registrare l’API nel serviceRegistry()
chiamando il suo registerApi(api)
.
Ecco un esempio di API personalizzata che verrà eseguita quando l’URL contiene /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)