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:

  1. Libreria QGIS Server: una libreria che fornisce un’API per la creazione di servizi web OGC.

  2. 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).

  3. 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:

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

QgsServerFilter

registerFilter()

Controllo accessi

QgsAccessControlFilter

registerAccessControl()

Cache

QgsServerCacheFilter

registerServerCache()

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 in GetCapabilities 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.5. authorizedLayerAttributes

Si usa per limitare la visibilità di un sottoinsieme specifico di attributi.

Il parametro attributo restituisce l’insieme corrente degli attributi visibili.

Ad esempio, per nascondere l’attributo ``role””:

def authorizedLayerAttributes(self, layer, attributes):
    return [a for a in attributes if a != "role"]
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)