Creating a QGIS Plugin

From CUOSGwiki
Jump to navigationJump to search

Disclaimer

This tutorial was created for Microsoft Windows platforms. It assumes a basic knowledge of GIS Environments and Python syntax.

Introduction

This tutorial contains instructions to aid you in the creation of your own QGIS plugins.

Data

As most of this tutorial is software based, the only data required is a shapefile layer for testing purposes at the end. One can find the shapefile used for testing here.

Be sure to keep all the downloaded files together,in the same project folder. Keep the folder pathway/filenames simple and clear.

      •	Ex.C:\Users\Larry\QGIS\Projects

Software Installation

Installing QGIS

The latest version of QGIS is good for this tutorial, it can either be the standalone QGIS version or OSGeo4W installer (both come equipped with some form of Qt creator). For QGIS installation refer to here.

Installing VS Code

Any kind of coding requires a text editor or IDE. If you have a preference then use it, but in this tutorial we will be doing things in VS Code. See here for installation.

Installing QGIS Plugins

Once QGIS is installed and open, go to the Plugins > Manage and Install Plugins menu and install: Plugin Reloader and Plugin Builder.

Plugin Builder: A useful plugin that creates all the files and boilerplate code required to get going with a plugin.

Plugin Reloader: A useful plugin that aids in testing and changing plugin code, without having to restart QGIS every time.

MW1.png Figure 1: QGIS plugins used.

Python Bindings

The plugin needs to be able to access the relevant Python bindings from the plugin folder, so in order to do this the path to the QGIS install must be indicated. To do this a 'Windows Batch File' (.bat) will need to be created with the following text (this can be done with notepad and the "save as" function):

@echo off
call "C:\OSGeo4W64\bin\o4w_env.bat"
call "C:\OSGeo4W64\bin\qt5_env.bat"
call "C:\OSGeo4W64\bin\py3_env.bat"

@echo on
pyrcc5 -o resources.py resources.qrc

Save this file to your project folder and name it compile.bat, but if you installed QGIS to a different path, replace C:\OSGeo4W64\bin\ with the location of your install.

Building the Basics

Now that all the installs have been completed, the basic plugin framework can be created. Open 'Plugin Builder' from the toolbar, Plugins > Plugin Builder > Plugin Builder and go through the dialog forms, as seen in Figures 2-3.

MW2 1.png Figure 2: Plugin builder input forms.

Be sure to fill the form with details pertaining to your plugin and don't forget to save it in a good location. The Class name will be the name of the Python Class/file containing the plugin instructions.

MW3.png Figure 3: Plugin builder final dialog prompt.

At the end of the dialogs you may receive a pyrcc5 error, but you can just ignore this.

Relocating the .bat

The compile.bat file created earlier can now be relocated to the plugin folder from a minute ago, as shown below in Figure 4. After the compile.bat file has been copied to the plugin folder, double click it and allow it to run. Next, copy the plugin directory to the QGIS plugin folder for your user profile. To do this, first locate the user profile folder by: opening QGIS and then select Settings > User Profiles > default (may vary for you) > Open Active Profile Folder. A new window will then open and this will be the profile folder. This can be seen below in Figures 5 & 6.

MW7.png Figure 4: compile.bat file copied to plugin folder.

MW5.png Figure 5: Opening the Active User Profile


MW4.png Figure 6: Active user profile folder.

In the new user profile window, open the python > plugins subfolder shown in Figure 6 and copy your entire plugin directory there.

Installing the New Plugin

Finally, we can have a first look at the plugin. Close QGIS and relaunch it. Then go to Plugins > Manage and Install plugins and enable the plugin ('DisplayInfo' in this case). There will now be a new icon on the plugin toolbar as seen in 'Figure

Create the Plugin UI

Adding the Code

Full Code

   # -*- coding: utf-8 -*-  
   """ 
   /*************************************************************************** 
    DisplayInfo 
                                    A QGIS plugin 
    This plugin displays a layers info in a .txt. 
    Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/ 
                                 ------------------- 
           begin                : 2019-11-09 
           git sha              : $Format:%H$ 
           copyright            : (C) 2019 by Mike 
           email                : euserver28@gmail.com 
    ***************************************************************************/ 
    
   /*************************************************************************** 
    *                                                                         * 
    *   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, QFileDialog  
   from qgis.core import QgsProject, Qgis  
     
   # Initialize Qt resources from file resources.py  
   from .resources import *  
   # Import the code for the dialog  
   from .display_info_dialog import DisplayInfoDialog  
   import os.path  
     
     
   class DisplayInfo:  
       """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',  
               'DisplayInfo_{}.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'&Display Info')  
     
           # 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('DisplayInfo', message)  
     
     
       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."""  
     
           icon_path = ':/plugins/display_info/icon.png'  
           self.add_action(  
               icon_path,  
               text=self.tr(u'Displayy Layer Info as TXT'),  
               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."""  
           for action in self.actions:  
               self.iface.removePluginMenu(  
                   self.tr(u'&Display Info'),  
                   action)  
               self.iface.removeToolBarIcon(action)  
     
       def select_output_file(self):  
           filename, _filter = QFileDialog.getSaveFileName(  
               self.dlg, "Select   output file ","", '*.txt')  
           self.dlg.lineEdit.setText(filename)  
     
       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 = DisplayInfoDialog()  
               self.dlg.pushButton.clicked.connect(self.select_output_file)  
     
           # Fetch the currently loaded layers  
           layers = QgsProject.instance().layerTreeRoot().children()  
           # Clear the contents of the comboBox from previous runs  
           self.dlg.comboBox.clear()  
           # Populate the comboBox with names of all the loaded layers  
           self.dlg.comboBox.addItems([layer.name() for layer in layers])  
     
           # show the dialog  
           self.dlg.show()  
           # Run the dialog event loop  
           result = self.dlg.exec_()  
           # See if OK was pressed  
           if result:  
               filename = self.dlg.lineEdit.text()  
               with open(filename, 'w') as output_file:  
                   selectedLayerIndex = self.dlg.comboBox.currentIndex()  
                   selectedLayer = layers[selectedLayerIndex].layer()  
                   test1 = selectedLayer.dataProvider().dataSourceUri() + '\n' #QgsProject.instance().readPath("./") + '\n'  
                   test2 = selectedLayer.extent() #  
                   (xmin, xmax, ymin, ymax) = (test2.xMinimum(), test2.xMaximum(), test2.yMinimum(), test2.yMaximum())  
                   xminString = str(xmin)  
                   xmaxString = str(xmax)  
                   yminString = str(ymin)  
                   ymaxString = str(ymax) #Turn into extent function?  
                   test3 = selectedLayer.crs().authid()  
                   test4 = selectedLayer.crs().description()  
                   output_file.write("Filepath: " + test1)  
                   output_file.write("X-min: " + xminString +"m" + '\n')  
                   output_file.write("X-max: " + xmaxString +"m"+ '\n')  
                   output_file.write("Y-min: " + yminString +"m" +'\n')  
                   output_file.write("Y-max: " + ymaxString +"m"+ '\n')  
                   output_file.write("CRS: " + test3 +" - " + test4)  
                   os.startfile(filename)  
               self.iface.messageBar().pushMessage(  
                   "Success", "Output file written at " + filename,  
                   level=Qgis.Success, duration=3)

Testing the New Plugin

References

1: https://gis-ops.com/qgis-simple-plugin/