Como criar seu primeiro plugin para QGIS usando Python e Qt Designer? Parte II

Confira nosso curso online de QGIS

Dê seus primeiros passos na criação de plugins com Python e QGIS utilizando o Qt Designer. Incremente seu plugin com checkbox e possibilitando ao usuário salvar os pontos criados.

Na postagem anterior, aprendemos a criar a base de arquivos para criarmos nosso plugin no QGIS. Nesta postagem, daremos continuidade nele e vamos salvar o arquivo shapefile criado (pois no código anterior, ele era temporário).

Além disso, iremos extrair, a partir do ponto criado, em qual parte de um determinado shapefile o ponto se localiza (por exemplo, em qual zoneamento da cidade o ponto se encontra).

Como salvar um shapefile no PyQGIS

Para salvar o shapefile criado, vamos acrescentar um campo no Qt Designer para que o usuário possa identificar o local onde o shape será salvo e em seguida vamos modificar o código Python para que ele armazene corretamente esse dado.

No Qt Design, iremos adicionar:

  • Um item “Label” com um texto mostrando para o usuário que o campo ao lado deve ser utilizado para colocar o endereço onde o shapefile será salvo;
  • Um item “Line Edit”, o qual irá mostrar o endereço escolhido; e
  • Um item “Push Button”, no qual o usuário irá clicar e uma janela que possibilitará navegar no Windows e selecionar o caminho para salvar o shapefile.

Após adicionar estes itens, iremos renomear (no Object Inspector) o Line Edit para “caminho”. O resultado é apresentado na figura abaixo.

Botão para guardar o caminho do shapefile.
Botão para guardar o caminho do shapefile.

Agora que já adicionamos a interface gráfica, vamos modificar o código python para que o nosso sistema funcione.

Antes da função “def run(self)”, vamos criar uma função para ser executada quando o usuário clica no botão “…” ao lado do nosso “Line Edit”. O código para ser utilizado é apresentado abaixo:

## Função para obter o caminho onde será salvo o shapefile 
def selecionar_saida(self):
   arquivoCaminho = QFileDialog.getSaveFileName(self.dlg, "Salvar o   arquivo em: ", "", "*.shp")
   self.dlg.caminho.setText(arquivoCaminho)

Agora, precisamos indicar por meio de python, que esta função será chamada quando apertarmos o botão “…”.

A função initGui é um bom local para adicionar conexões entre os botões e as ações executadas quando estes são pressionados (Germán Carrillo no GIS StackExchange).

Para isso, vamos até o topo do código python na função initGui e iremos inserir as seguintes linhas de código, onde a primeira linha limpa o campo (caso este já tenha sido preenchido) e o segundo indica que a função “selecionar_saida” deverá ser chamada quando ele for clicado.

## Esse código deverá ser inserido dentro de def initGui(self). 
self.dlg.caminho.clear() 
self.dlg.pushButton.clicked.connect(self.selecionar_saida)

Note que ainda precisamos modificar o código apresentado na postagem anterior para que este receba como variável o endereço onde salvamos o shapefile.

Apresentamos abaixo o código da postagem anterior mais o código adicionado nesta postagem (o qual é iniciado com duas hashtags ##). Lembrando que este código foi inserido dentro da função def run(self) (e dentro de if result:).

# Variáveis que recebem as coordenadas fornecidas pelo usuário (e projeção) 
longX = self.dlg.longTextIn.text() 
latY = self.dlg.latTextIn.text() 
projecao = self.dlg.projEPSG.text() 

## Variável com o caminho salvo 
localSalvo = self.dlg.caminho.text() 

# Cria um shapefile de ponto a partir das coordenadas fornecidas 
# Definindo a geometria do shapefile 
camada = QgsVectorLayer('Point?crs=epsg:'+projecao, 'point' , 'memory') 

# Define o provedor os pontos fornecidos 
prov = camada.dataProvider() 
prov.addAttributes([QgsField("Nome", QVariant.String)]) 

## Fornece atributos ao nosso ponto ponto = QgsPoint(float(longX),float(latY)) 

# Adiciona uma nova feição para a geometria 
feat = QgsFeature() feat.setGeometry(QgsGeometry.fromPoint(ponto)) feat.setAttributes(["Ponto B2E"]) 

## Linha adicionada para fornecer atributo ao ponto prov.addFeatures([feat]) 

# Atualiza a camada 
camada.updateExtents() 
camada.updateFields() 

## Atualiza os campos adicionados 
# Adiciona a camada ao QGIS QgsMapLayerRegistry.instance().addMapLayers([camada]) 

## Salva a camada na variável localSalvo QgsVectorFileWriter.writeAsVectorFormat(camada, localSalvo, "utf_8_encode", camada.crs(), "ESRI Shapefile") 
pnt_layer = QgsVectorLayer(localSalvo, "Ponto B2E", "ogr")

Desta forma, conseguimos criar um shapefile e salvá-lo no nosso computador. Agora vamos criar uma função para estabelecer uma área de interesse e verificar se nosso ponto esta ou não dentro dela.

Lembre-se de adicionar, no topo do código python, junto com com os outros códigos do tipo from … import …, a seguinte linha “from PyQt4.QtCore import *”.

Intersecção de Ponto e Polígonos no PyQGIS

Agora, iremos criar um campo no nosso plugin onde o usuário irá marcar qual é a área de interesse (ou shapefile) que este deseja avaliar. Em outras palavras, vamos responder a seguinte pergunta: em qual zoneamento/bacia hidrográfica/região o ponto criado está inserido?

Para este tutorial, iremos utilizar dois shapefiles, um deles contendo os limites das bacias hidrográficas do município do Rio de Janeiro e outro com as regiões de planejamento do mesmo município.

Cabe lembrar que para executar a intersecção, os shapefiles envolvidos devem estar no mesmo sistema de coordenadas.

Os dois shapefiles indicados estão em um sistema de coordenadas antigo (SAD69) e no nosso tutorial, reprojetamos os shapefiles para SIRGAS 2000 UTM Zone 23S (ESPG: 31983).

Após realizar o download dos shapefiles, no Qt Designer, iremos adicionar dois Check Box, os quais indicarão para o usuário qual camada será avaliada, conforme a caixa esta marcada ou não.

Um dos checkbox será para o limite das bacias hidrográficas e outro para o limite das regiões de planejamento. O nome de cada um deles no Object Inspector é checkBoxRH e checkBoxRP, respectivamente.

Check Box adicionados no Qt Designer.
Check Box adicionado no Qt Designer e seu respectivo nome no Object Inspector.

Agora que já temos nossas caixas para marcar a área de interesse, vamos adicionar o código que irá realizar a intersecção entre o ponto adicionado pelo usuário e a área selecionada.

O código seguinte deve ser inserido abaixo do código que apresentamos anteriormente, e em caso de dúvida, o código esta comentado, de forma a esclarecer as funções utilizadas.

## [....] Continuação do código anterior. 
## Salva a camada na variável localSalvo QgsVectorFileWriter.writeAsVectorFormat(camada, localSalvo, "utf_8_encode", camada.crs(), "ESRI Shapefile") 
pnt_layer = QgsVectorLayer(localSalvo, "Ponto B2E", "ogr") 

## Variáveis para a interseções 
pnt_selection = [] 
bh_selection = [] 
rp_selection = [] 

## Condições para os checkboxs criados (Avaliação da Bacia Hidrográfica e da Região de Planejamento) 
## Primeira condição para avaliar se o ponto cai em alguma bacia hidrográfica 
if self.dlg.checkBoxBH.isChecked():
   bh_rioPath = "C:/Users/ferna/Desktop/municipiosRJ/bacia_hidroRJ_SIRGAS.shp" # Não esqueça de corrigir esse caminho no seu computador
   bh_rioLayer = QgsVectorLayer(bh_rioPath, "BH RJ", "ogr")
   for w in pnt_layer.getFeatures():
     for s in bh_rioLayer.getFeatures():
       if s.geometry().intersects(w.geometry()):
         ## Número dois foi usado pois o nome da bacia esta na terceira coluna (python começa a contar do zero)
         bh_selection.append(s.attributes()[2])
         pnt_selection.append(w.attributes()[0])
         break
   print pnt_selection[0] + " esta na " + bh_selection[0]
 elif not self.dlg.checkBoxBH.isChecked():
   print u"O item Bacias Hidrográficas não foi selecionado."

## Segunda condição para avaliar se o ponto cai em alguma região de planejamento
 if self.dlg.checkBoxRP.isChecked():
   rp_rioPath = "C:/Users/ferna/Desktop/municipiosRJ/limite_RP_SIRGAS.shp" # Não esqueça de corrigir esse caminho no seu computador
   rp_rioLayer = QgsVectorLayer(rp_rioPath, "RP RJ", "ogr")
   for w in pnt_layer.getFeatures():
     for s in rp_rioLayer.getFeatures():
       if s.geometry().intersects(w.geometry()):
         ## Número três foi usado pois o nome da região esta na quarta coluna (python começa a contar do zero)
         rp_selection.append(s.attributes()[3])
         pnt_selection.append(w.attributes()[0])
         break
   print pnt_selection[0] + u" esta na região de " + rp_selection[0]
 elif not self.dlg.checkBoxRP.isChecked():
   print u"O item Região de Planejamento não foi selecionado."

Note que as rotinas (loops) para a avaliação da intersecção são semelhantes e terminam com break, de forma a realizar o loop apenas uma vez.

Você pode acessar ele clicando em Plugins > Python Controle, ou pelo atalho Ctrl + Alt + P.

O comando print do python irá exibir as mensagens que inserimos nesta linha, sendo que quando executamos o plugin no QGIS, essas mensagens serão exibidas no terminal python dele.

Plugin desenvolvido rodando e mensagens no terminal python.
Plugin desenvolvido rodando e mensagens no terminal python.

O código que apresentamos irá funcionar corretamente se o usuário inserir pontos dentro das áreas de interesse, caso um ponto fora seja fornecido, um erro será gerado.

Então, como podemos evitar esse erro e só mostrar uma mensagem avisando o usuário que o ponto não esta dentro dos limites?

Tratando erros dentro do Python

Nesta situação, utilizaremos um bloco de código do tipo “try: …. except: ….”, onde o código que pode apresentar erro é inserido depois de try (tentar) e caso algum erro aconteça, o que o programa deve fazer é colocado depois de except.

Desta forma, no nosso código, onde havia somente “print pnt_selection[0] + ” esta na ” + bh_selection[0]”, substitua pelo código abaixo, sendo que o erro levantado, caso o ponto caia fora da área de interesse é do tipo IndexError.

try:
   print pnt_selection[0] + " esta na " + bh_selection[0]
 except IndexError:
   print u"O ponto fornecido esta fora da área de interesse!!"

E chegamos ao fim da segunda parte do nosso tutorial de como criar um plugin no QGIS utilizando Qt Designer e Python. Você pode conferir o código completo deste tutorial clicando aqui >> ponto_exatoB2E (Obs.: Abra o arquivo de texto no NotePad++ para que a indentação fique correta).

Em breve, iremos postar a terceira parte. E caso você tenha alguma dúvida, deixe ela nos comentários que responderemos assim que possível.

 Referências consultadas: 
Ujaval Gandhi - QGIS Tutorials and Tips: https://www.qgistutorials.com/en/docs/building_a_python_plugin.html Python: How to List Polygon Intersections in QGIS: https://gifguide2code.com/2017/04/16/python-how-to-code-a-list-of-polygon-intersections-in-qgis/


Clique na figura abaixo e assine nossa lista de emails para receber nosso ebook "Como criar mapas de localização com ArcGIS 10.x".

Apostila Mapa de Localização Banner

Author: Fernando BS

Engenheiro Ambiental e de Segurança do Trabalho. Atua nas áreas de geoprocessamento, mineração e hidrologia. Busca soluções utilizando softwares como QGIS, R e Python.

12 thoughts on “Como criar seu primeiro plugin para QGIS usando Python e Qt Designer? Parte II”

  1. Senhores Bom Dia,
    Parabéns pelos tutoriais, completos e didáticos.
    Estou desenvolvendo um plugin que chama um programa externo. Como para cada usuário e maquina este endereço será diferente. Eu solicitarei que o endereço do programa na maquina
    do novo usuário seja inserido em um arquivo txt e leio este arquivo, com o conteúdo, por exemplo:.
    “C:\Program Files (x86)\SAGA-GIS\saga_gui.exe”
    Porem quando executo no plugin o codigo para carregar este programa:
    ref_arquivo = open(“c:/mais_valia/saga.txt”,”r”)
    linha1 = ref_arquivo.read()
    os.startfile(linha1)
    O QGIS me devolve este erro:
    Traceback (most recent call last):
    File “C:/Users/JOAO/.qgis2/python/plugins\BB_Class\BB_Module_Name.py”, line 214, in run
    os.startfile(linha1)
    WindowsError: [Error 2] O sistema n�o pode encontrar o arquivo especificado: ‘”C:\\Program Files (x86)\\SAGA-GIS\\saga_gui.exe”\n’
    No comando os.startfile ele esta considerando uma barra a mais. Haveria uma forma para contornar isso?
    P.S. Quando insiro o caminho no comando, dai funciona.
    os.startfile(“C:\Program Files (x86)\SAGA-GIS\saga_gui.exe”)
    Muito Obrigado.

  2. Boa tarde…
    O link para baixar os shapes deste tutorial estão quebrados..
    Mas consegui fazer ate o final usando dois shapes que eu tenho aqui ( grade_landsat e Municipios do Brasil).
    Foi bom pois alterei algumas coisas no codigo e o campo do atributo era diferente, mas com os comentarios de vcs no programa python ficou mais facil de fazer as alterações.
    Muito legal o tutorial… é bom ver os exercicios surtindo efeito…

    Bora pra parte 3…

    abraços

    1. Bom dia Sérgio,

      Obrigado pelos comentários. Com relação aos links quebrados, já corrigimos eles, mas já deixo aqui o link para o site da Prefeitura do Rio de Janeiro para o acessos aos diferentes dados que eles disponibilizam: https://www.data.rio/

    1. Bom dia Sandro,

      Normalmente eu vou escrevendo o código e quando finalizo uma parte dele (ex. um botão e as ações vinculadas à ele), eu abro o QGIS e testo se ele esta funcionando corretamente.

      Também utilizo o Plugin Reloader para não precisar ficar fechando o QGIS quando tiver feito alguma alteração no código (comentei deste plugin na primeira postagem).

      1. Sim, eu vi vc sugerindo a instalação dele. Apenas achei que daria pra rodar dentro de uma IDE, como pycharm. Ficaria mais fácil se fosse possível. Fica aí a dica, se alguém souber posta aí.
        Obrigado!!!

  3. Ola Fernando,
    Tenho no meu plugin 3 radioButton, identificados como radioButton, radioButton_2 e radioButton_3 quando um deles é acionado qual o comando devo testar?
    Tentei este If self.dlg.radioButton.isChecked() : porem não funcionou.
    Obrigado.

  4. Boa tarde!!
    Implementando este código, me ocorreu que as linhas responsáveis por limpar o campo do caminho, e por chamar a função “selecionar_saida” quando o botão “…” for clicado, não funcionaram no initGUI.
    Quando coloquei elas imediatamente após o run(self), o programa funcionou perfeitamente.
    Isto seria tecnicamente errado? Ou teria alguma explicação do porquê não ter funcionado no initGUI?
    O erro que dava era ” AttributeError: pontoExato instance has no attribute ‘dlg’ “.

    Muito Obrigado!

    1. Boa tarde Luccas,
      Não sei te responder se estaria errado, mas se esta funcionando, melhor 🙂 – É importante ir testando para verificar se o plugin não daria mais erros.
      Obrigado também por compartilhar seu comentário, tenho certeza que ele poderá ajudar outros caso eles tenham o mesmo problema.

Deixe um comentário para Fernando BS Cancelar resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *