É possível criar scripts para integrar a “Processing Toolbox” de forma simples, rápida e desta forma aliar a interface gráfica do QGIS com a versatilidade de um script Python.
Recentemente necessitei de calcular a área ocupada pelos pixeis de um raster cujos valores se encontram dentro de um determinado intervalo. Como não encontrei uma solução rápida que me agradasse, e talvez um pouco por preguiça, optei por fazer um pequeno algoritmo em Python para o Processing que demorou apenas uns minutos e que vamos aqui exemplificar.
Existem duas formas de escrever um script para o “Processing“:
- Utilizando o QgsProcessingAlgorithm
- Utilizando o @alg que iremos abordar neste tutorial
Para criar um script basta selecionar a opção “Create new script” no menu Python
Esta opção irá abrir o editor de scripts do “Processing” onde iremos escrever o nosso script Python.
@alg
O decorador @alg vai-nos permitir de forma simples comunicar com a “Processing Toolbox” e assim definir por exemplo o nome e localização do nosso algoritmo, inputs e outputs. Para mais informações podemos consultar o seguinte link https://docs.qgis.org/3.10/en/docs/user_manual/processing/scripts.html?highlight=python#scripts-alg
O algoritmo
O nosso script tem o objectivo de calcular a área de um raster que se encontra numa determinada gama de valores. Para isso vamos executar os seguintes passos:
- Definir os inputs, outputs, nome e localização do algoritmo
- Abrir o raster
- Extrair alguns parâmetros utilizando o pyQGIS. Em alternativa tambem poderiamos usar o GDAL.
- Abrir o raster com o GDAL, desta forma exemplificamos como se converte um raster de pyQGIS para GDAL e assim usufruir as muitas capacidades deste último
- Converter o raster para um array numpy
- Calcular o número de pixeis que se encontram no intervalo de valores com numpy
- Calcular a área
- Retornar os valores previamente calculados.
O script
from qgis import processing from qgis.processing import alg from qgis.core import QgsProject from osgeo import gdal, gdal_array @alg(name='countpixels', label='Count pixels from a raster for a given range', group='myscripts', group_label='My scripts') @alg.input(type=alg.RASTER_LAYER, name='INPUT', label='Input raster layer') @alg.input(type=alg.BAND, name='BAND',defaultValue=1, parentLayerParameterName='INPUT', label='Raster band') @alg.input(type=alg.NUMBER, name='MINIMUM', label='Min Value', default=0) @alg.input(type=alg.NUMBER, name='MAXIMUM', label='Max Value', default=0.9) @alg.output(type=alg.NUMBER, name='NUMBEROFPIXELS', label='Number of pixels') @alg.output(type=alg.NUMBER, name='XSIZE', label='X pixel size') @alg.output(type=alg.NUMBER, name='YSIZE', label='Y pixel size') @alg.output(type=alg.NUMBER, name='AREA', label='Total Area') def countpixels(instance, parameters, context, feedback, inputs): """ Description of the algorithm. (If there is no comment here, you will get an error) """ #parameters input_rasterLayer = instance.parameterAsRasterLayer(parameters, 'INPUT', context) input_rasterBand = instance.parameterAsInt(parameters, 'BAND', context) pixelSizeX= input_rasterLayer.rasterUnitsPerPixelX() pixelSizeY= input_rasterLayer.rasterUnitsPerPixelY() ds=gdal.Open(input_rasterLayer.source()) band1Array=ds.GetRasterBand(input_rasterBand).ReadAsArray() size=band1Array[(parameters['MINIMUM'] < band1Array) & (band1Array < parameters['MAXIMUM'])].size # Calculate are in square km area= pixelSizeX*pixelSizeY*size*0.000001 if feedback.isCanceled(): return {} return {'NUMBEROFPIXELS': size, 'XSIZE': pixelSizeX,'YSIZE': pixelSizeY, 'AREA': area}
Como já descrito o decorador @alg é muito util, com ele definimos em primeiro lugar, o nome do nosso script, o titulo e o grupo em que este vai aparecer na “Processing Toolbox“.
Nas linhas seguintes vamos definir os vários inputs nomeadamente:
- O raster;
- A banda do raster, importante realçar a importância da tag parentLayerParameterName de forma a podermos definir qual o raster à qual a banda se refere;
- Os valores de mínimo e máximo.
Depois dos inputs definimos os outputs que no nosso caso são apenas valores numéricos. Porém poderiam ser camadas raster ou vectoriais.
Por fim criamos a função principal com o mesmo nome que foi definido anteriormente no primeiro @alg com o nome do algoritmo. De realçar que a docstring na função principal é obrigatória e vai posteriormente aparecer no menu de ajuda. Acrescento ainda que no nosso caso, apenas temos de retornar um dicionário com os valores e associá-los às variáveis predefinidas no output.
Ao terminar de escrever o script no editor de scripts podemos guardar e ao clicar em “Run Script“, caso não haja nenhum erro de syntax, este irá abrir a janela com o nosso script.
A nossa janela irá aparecer automaticamente sem ser necessário qualquer código para controlar a GUI.
A partir deste momento o script deverá aparecer na “Processing Toolbox” dentro do grupo que definimos anteriormente no @alg
Ao correr o script iremos obter no log os resultados pretendidos. Este log é importante também para efeitos de “debug” pois para além dos resultados vão ser apresentados outros valores tais como os inputs, entre outros.
Importa realçar algumas regras de boas práticas:
- Não carregar as camadas resultantes no script. Deve-se deixar o “Processing” carregar as camadas definidas no output;
- Devemos sempre declarar as variáveis de output;
- Não se devem carregar elementos do GUI ou “message boxes” com o script. Deve-se em alternativa utilizar o objecto de feedback QgsProcessingFeedback ou então QgsProcessingException
Como podemos ver através deste post, é muito fácil criar um algoritmo no “Processing” com apenas alguns conhecimentos de Python. Com o decorador @alg torna-se bastante simples deixar o “Processing” criar a GUI e assim focarmos-nos no que mais interessa, o nosso código.
Pessoalmente sou um fã do GDAL/OGR e numpy em geoprocessamento, pelo que sempre que posso utilizo-os nos meus scripts, estas bibliotecas já vêem no QGIS e podemos utilizá-las facilmente no “Processing“.