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

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".


Author: Fernando BS

Engenheiro Ambiental e de Segurança do Trabalho. Atua nas áreas de recuperação ambiental, geoprocessamento e ciência do solo. Busca soluções utilizando softwares como ArcGIS, R e MATLAB.

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

Deixe uma resposta

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