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.
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.
- Shapefile com os limites das bacias hidrográficas do Rio de Janeiro;
- Shapefile com os limites das regiões de planejamento do Rio de Janeiro.
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.
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.
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/
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.
Boa noite João,
Algumas sugestões para você tentar: 1) Trocar para apenas uma barra, ou tentar a barra invertida; 2) Tente carregar as linhas que estão no arquivo de texto e depois executar elas com o os.startfile() (exemplo: https://www.blog.pythonlibrary.org/2010/09/04/python-101-how-to-open-a-file-or-program/)
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
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/
Como vc fez para ir testando o código à medida que foi escrevendo?
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).
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!!!
Conheço o PyCharm, mas quando montei esse tutorial, nem havia pensado nele.
Há alguns tutoriais ensinando como configurar o PyCharm para usar o PyQGIS e Qt.
QGIS 2: https://nathanw.net/2014/05/10/setting-up-pycharm-for-pyqgis-and-qt/
QGIS 3: http://spatialgalaxy.net/2018/02/13/quick-guide-to-getting-started-with-pyqgis3-on-windows/
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.
Bom dia João,
Ao rodar ele, é fornecido alguma mensagem de erro? Pois você pode procurar por ela no google para ver se alguem já teve um problema semelhante.
Fiz uma busca por PyQt QRadioButton e aparentemente os Radio Buttons funcionam um pouco diferente dos Check Boxes, você pode conferir alguns exemplos de código em:
https://pythonbasics.org/pyqt-radiobutton/
https://www.tutorialspoint.com/pyqt/pyqt_qradiobutton_widget.htm
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!
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.