I was tasked with making a small plugin for QGIS or add a small functionality to an existing one.
What I am trying to do is take the "geocode with web service" from the plugin MMQGIS and add a button that opens a new dialog window with an option to split a CSV file
I first created a plugin to split a CSV file using plugin builder. Now I am trying to add the existing code for "geocode with web service" and I get some errors like:
'NewMmqgis' object has no attribute 'dlg2'
and
'mmqgis_geocode_web_service_dialog' object has no attribute 'status'
I can't understand where the problem is.
I think setupUI
doesn't set up the attributes for my mmqgis_geocode_web_service_dialog
but I don't know how to fix it.
Here is my code for dialogs:
new_mmqgis_dialog.py
# -*- coding: utf-8 -*-
"""
/***************************************************************************
NewMmqgisDialog
A QGIS plugin
upgraded version of mmqgis geocode function
Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/
-------------------
begin : 2022-01-14
git sha : $Format:%H$
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
"""
import os
import pandas as pd
from qgis.PyQt.QtWidgets import QMessageBox
from qgis.core import QgsVectorLayer
from qgis.PyQt import uic
from qgis.PyQt import QtWidgets
from qgis.core import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
# Used instead of "import math" so math functions can be used without "math." prefix
from math import *
# This loads your .ui file so that PyQt can populate your plugin with the elements from Qt Designer
FORM_CLASS, _ = uic.loadUiType(os.path.join(
os.path.dirname(__file__), 'mmqgis_geocode_csv_spliter_form.ui'))
FORM_CLASS2, _ = uic.loadUiType(os.path.join(
os.path.dirname(__file__), 'new_mmqgis_dialog_base.ui'))
class NewMmqgisDialog(QtWidgets.QDialog, FORM_CLASS):
def __init__(self, parent=None):
"""Constructor."""
super(NewMmqgisDialog, self).__init__(parent)
# Set up the user interface from Designer through FORM_CLASS.
# After self.setupUi() you can access any designer object by doing
# self.<objectname>, and you can use autoconnect slots - see
# http://qt-project.org/doc/qt-4.8/designer-using-a-ui-file.html
# #widgets-and-dialogs-with-auto-connect
self.setupUi(self)
self.buttonBox.button(QtWidgets.QDialogButtonBox.Apply).clicked.connect(self.run)
self.buttonBox.button(QtWidgets.QDialogButtonBox.Close).clicked.connect(self.reject)
self.input_csv_name.fileChanged.connect(self.set_csv_attributes)
chars = [".", ",", "space", "-", "/", ";", "'"]
self.comboBox_2.clear()
self.comboBox_2.addItems(chars)
self.comboBox_2.setCurrentIndex(0)
def set_csv_attributes(self):
self.comboBox.clear()
self.comboBox.addItem("(none)")
self.comboBox.setCurrentIndex(0)
input_csv = QgsVectorLayer(str(self.input_csv_name.filePath()))
if len(input_csv.fields()) <= 0:
# self.comboBox.addItem(str(self.input_csv_split.filePath()))
return
for index, field in enumerate(input_csv.fields()):
field_name = field.name()
self.comboBox.addItem(field_name)
def run(self):
input_csv_name = str(self.input_csv_name.filePath()).strip()
column = ""
if self.comboBox.currentText() and (self.comboBox.currentText() != "(none)"):
column = str(self.comboBox.currentText()).strip()
char = str(self.comboBox_2.currentText()).strip()
message = mmqgis_split_column(input_csv_name, column, char)
if message == None:
QMessageBox.critical(self.iface.mainWindow(), "errore", message)
self.accept()
class mmqgis_geocode_web_service_dialog(QtWidgets.QDialog, FORM_CLASS2):
def __init__(self, parent=None):
"""Constructor."""
super(mmqgis_geocode_web_service_dialog, self).__init__(parent)
self.setupUi(self)
self.mmqgis_set_status_bar(self.status)
self.buttonBox.button(QtWidgets.QDialogButtonBox.Apply).clicked.connect(self.run)
# self.editButton.clicked.connect(self.edit_cvs_file)
self.input_csv_name.fileChanged.connect(self.set_csv_attributes)
web_services = ["Google", "OpenStreetMap / Nominatim", "US Census Bureau", "ESRI Server", "NetToolKit"]
self.web_service.clear()
self.web_service.addItems(web_services)
self.web_service.setCurrentIndex(0)
self.web_service.currentIndexChanged.connect(self.service_changed)
self.duplicate_handling.addItems(["Use Only First Result", "Multiple Features for Multiple Results"])
self.mmqgis_initialize_spatial_output_file_widget(self.output_file_name)
self.mmqgis_initialize_tabular_output_file_widget(self.not_found_file_name)
def mmqgis_set_status_bar(self, status_bar):
status_bar.setMinimum(0)
status_bar.setMaximum(100)
status_bar.setValue(0)
status_bar.setFormat("Ready")
self.status_bar = status_bar
def service_changed(self):
if str(self.web_service.currentText()).strip() == "ESRI Server":
self.api_key_label.setText("ESRI Server URL")
self.api_key.setEnabled(True)
else:
self.api_key_label.setText("API Key")
self.api_key.setEnabled(str(self.web_service.currentText()).strip()
in ["Google", "NetToolKit"])
self.parameter_attribute_4.setEnabled(str(self.web_service.currentText()).strip() != "US Census Bureau")
def set_csv_attributes(self):
combolist = [self.parameter_attribute_1, self.parameter_attribute_2,
self.parameter_attribute_3, self.parameter_attribute_4]
for box in combolist:
box.clear()
box.addItem("(none)")
box.setCurrentIndex(0)
input_csv = QgsVectorLayer(str(self.input_csv_name.filePath()))
if len(input_csv.fields()) <= 0:
return
for index, field in enumerate(input_csv.fields()):
field_name = field.name()
for box in combolist:
box.addItem(field_name)
if field_name.lower().find("addr") >= 0:
self.parameter_attribute_1.setCurrentIndex(index + 1)
if field_name.lower().find("street") >= 0:
self.parameter_attribute_2.setCurrentIndex(index + 1)
if field_name.lower().find("city") >= 0:
self.parameter_attribute_2.setCurrentIndex(index + 1)
if field_name.lower().find("state") >= 0:
self.parameter_attribute_3.setCurrentIndex(index + 1)
if field_name.lower() == "st":
self.parameter_attribute_3.setCurrentIndex(index + 1)
if field_name.lower().find("province") >= 0:
self.parameter_attribute_3.setCurrentIndex(index + 1)
if field_name.lower().find("country") >= 0:
self.parameter_attribute_4.setCurrentIndex(index + 1)
def run(self):
input_csv_name = str(self.input_csv_name.filePath()).strip()
parameters = []
if self.parameter_attribute_1.currentText() and (self.parameter_attribute_1.currentText() != "(none)"):
parameters.append((str(self.parameter_name_1.text()).strip(),
str(self.parameter_attribute_1.currentText()).strip()))
if self.parameter_attribute_2.currentText() and (self.parameter_attribute_2.currentText() != "(none)"):
parameters.append((str(self.parameter_name_2.text()).strip(),
str(self.parameter_attribute_2.currentText()).strip()))
if self.parameter_attribute_3.currentText() and (self.parameter_attribute_3.currentText() != "(none)"):
parameters.append((str(self.parameter_name_3.text()).strip(),
str(self.parameter_attribute_3.currentText()).strip()))
if self.parameter_attribute_4.currentText() and (self.parameter_attribute_4.currentText() != "(none)"):
parameters.append((str(self.parameter_name_4.text()).strip(),
str(self.parameter_attribute_4.currentText()).strip()))
web_service = str(self.web_service.currentText()).strip()
api_key = str(self.api_key.displayText()).strip()
if (api_key == "(none)") or (api_key == ""):
api_key = None
use_first = str(self.duplicate_handling.currentText()) == "Use Only First Result"
output_file_name = str(self.output_file_name.filePath())
not_found_file_name = self.not_found_file_name.filePath()
message = mmqgis_geocode_web_service(input_csv_name, parameters, web_service, api_key, use_first,
output_file_name, not_found_file_name, self.mmqgis_status_callback)
if message != None:
QMessageBox.critical(self.iface.mainWindow(), "Web Service Geocode", message)
elif self.mmqgis_find_layer_by_data_source(output_file_name):
self.iface.mapCanvas().refreshAllLayers()
else:
self.iface.addVectorLayer(output_file_name, "", "ogr")
def mmqgis_split_column(csv, column, char):
data1 = pd.read_csv(csv, encoding='ISO-8859-8')
data1 = pd.concat([data1, data1[column].str.split(char, expand=True)], axis=1)
outfile = csv.split('.')[0] + "_split." + csv.split('.')[1]
data1.to_csv(outfile, encoding='ISO-8859-8', index=False)
return outfile
def mmqgis_geocode_web_service(input_csv_name, parameter_attributes, web_service, api_key, use_first,
output_file_name, not_found_file_name, status_callback=None):
#indentical to mmqgis's function
Here is my code for the plugin:
new_mmqgis.py
# -*- coding: utf-8 -*-
"""
/***************************************************************************
NewMmqgis
A QGIS plugin
upgraded version of mmqgis geocode function
Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/
-------------------
begin : 2022-01-14
git sha : $Format:%H$
copyright : (C) 2022 by beni segal
email : [email protected]
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
"""
from qgis.PyQt.QtCore import QSettings, QTranslator, QCoreApplication
from qgis.PyQt.QtGui import QIcon
from qgis.PyQt.QtWidgets import QAction
# Initialize Qt resources from file resources.py
from .resources import *
# Import the code for the dialog
from .new_mmqgis_dialog import *
import os.path
class NewMmqgis:
"""QGIS Plugin Implementation."""
def __init__(self, iface):
"""Constructor.
:param iface: An interface instance that will be passed to this class
which provides the hook by which you can manipulate the QGIS
application at run time.
:type iface: QgsInterface
"""
# Save reference to the QGIS interface
self.iface = iface
# initialize plugin directory
self.plugin_dir = os.path.dirname(__file__)
# initialize locale
locale = QSettings().value('locale/userLocale')[0:2]
locale_path = os.path.join(
self.plugin_dir,
'i18n',
'NewMmqgis_{}.qm'.format(locale))
if os.path.exists(locale_path):
self.translator = QTranslator()
self.translator.load(locale_path)
QCoreApplication.installTranslator(self.translator)
# Declare instance attributes
self.actions = []
self.menu = self.tr(u'&new mmqgis geocoder')
# Check if plugin was started the first time in current QGIS session
# Must be set in initGui() to survive plugin reloads
self.first_start = None
# noinspection PyMethodMayBeStatic
def tr(self, message):
"""Get the translation for a string using Qt translation API.
We implement this ourselves since we do not inherit QObject.
:param message: String for translation.
:type message: str, QString
:returns: Translated version of message.
:rtype: QString
"""
# noinspection PyTypeChecker,PyArgumentList,PyCallByClass
return QCoreApplication.translate('NewMmqgis', message)
def mmqgis_add_submenu(self, submenu):
if self.mmqgis_menu != None:
self.mmqgis_menu.addMenu(submenu)
else:
self.iface.addPluginToMenu("&mmqgis", submenu.menuAction())
def add_action(
self,
icon_path,
text,
callback,
enabled_flag=True,
add_to_menu=True,
add_to_toolbar=True,
status_tip=None,
whats_this=None,
parent=None):
"""Add a toolbar icon to the toolbar.
:param icon_path: Path to the icon for this action. Can be a resource
path (e.g. ':/plugins/foo/bar.png') or a normal file system path.
:type icon_path: str
:param text: Text that should be shown in menu items for this action.
:type text: str
:param callback: Function to be called when the action is triggered.
:type callback: function
:param enabled_flag: A flag indicating if the action should be enabled
by default. Defaults to True.
:type enabled_flag: bool
:param add_to_menu: Flag indicating whether the action should also
be added to the menu. Defaults to True.
:type add_to_menu: bool
:param add_to_toolbar: Flag indicating whether the action should also
be added to the toolbar. Defaults to True.
:type add_to_toolbar: bool
:param status_tip: Optional text to show in a popup when mouse pointer
hovers over the action.
:type status_tip: str
:param parent: Parent widget for the new action. Defaults None.
:type parent: QWidget
:param whats_this: Optional text to show in the status bar when the
mouse pointer hovers over the action.
:returns: The action that was created. Note that the action is also
added to self.actions list.
:rtype: QAction
"""
icon = QIcon(icon_path)
action = QAction(icon, text, parent)
action.triggered.connect(callback)
action.setEnabled(enabled_flag)
if status_tip is not None:
action.setStatusTip(status_tip)
if whats_this is not None:
action.setWhatsThis(whats_this)
if add_to_toolbar:
# Adds plugin icon to Plugins toolbar
self.iface.addToolBarIcon(action)
if add_to_menu:
self.iface.addPluginToMenu(
self.menu,
action)
self.actions.append(action)
return action
def initGui(self):
"""Create the menu entries and toolbar icons inside the QGIS GUI."""
self.mmqgis_menu = QtWidgets.QMenu(QCoreApplication.translate("mmqgis", "MMQGIS"))
self.iface.mainWindow().menuBar().insertMenu(self.iface.firstRightStandardMenu().menuAction(), self.mmqgis_menu)
self.geocode_menu = QtWidgets.QMenu(QCoreApplication.translate("mmqgis", "&Geocode"))
self.mmqgis_add_submenu(self.geocode_menu)
icon = QIcon('icon.png')
self.geocode_web_service_action = QtWidgets.QAction(icon, "Geocode CSV with Web Service",
self.iface.mainWindow())
self.geocode_web_service_action.triggered.connect(self.geocode_web_service)
self.geocode_menu.addAction(self.geocode_web_service_action)
icon = QIcon('icon.png')
self.geocode_csv_spliter_action = QtWidgets.QAction(icon, "Csv spliter", self.iface.mainWindow())
self.geocode_csv_spliter_action.triggered.connect(self.run)
self.geocode_menu.addAction(self.geocode_csv_spliter_action)
# icon_path = ':/plugins/new_mmqgis/icon.png'
# self.add_action(
# icon_path,
# text=self.tr(u'split CSV'),
# callback=self.run,
# parent=self.iface.mainWindow())
# will be set False in run()
self.first_start = True
def unload(self):
"""Removes the plugin menu item and icon from QGIS GUI."""
if self.mmqgis_menu != None:
self.iface.mainWindow().menuBar().removeAction(self.mmqgis_menu.menuAction())
else:
self.iface.removePluginMenu("&mmqgis", self.geocode_menu.menuAction())
def run(self):
"""Run method that performs all the real work"""
# Create the dialog with elements (after translation) and keep reference
# Only create GUI ONCE in callback, so that it will only load when the plugin is started
if self.first_start == True:
self.first_start = False
self.dlg = NewMmqgisDialog()
# show the dialog
self.dlg.show()
# Run the dialog event loop
result = self.dlg.exec_()
# See if OK was pressed
if result:
# Do something useful here - delete the line containing pass and
# substitute with your code.
pass
def geocode_web_service(self):
if self.first_start == True:
self.first_start = False
self.dlg2 = mmqgis_geocode_web_service_dialog()
# show the dialog
self.dlg2.show()
# Run the dialog event loop
result = self.dlg2.exec_()
# See if OK was pressed
if result:
# Do something useful here - delete the line containing pass and
# substitute with your code.
pass
For now, the CSV splitter is in a different submenu for testing.
I tested and it works just fine. However, I can't get the geocode with web service to work for now.
The UI is identical to mmqgis plugin with the addition of 1 button named editButton
which is disabled for now.
So it does have a qprogressbar
element named status
.
Can anyone see what I am doing wrong?
I resorted to building a new plugin with plugin builder instead of building on the original mmqgis plugin because of a similar error.
self.status
? A widget innew_mmqgis_dialog_base.ui
?