Difference between revisions of "Creating a QGIS Plugin"
JohnFoster (talk | contribs) m (→Disclaimer) |
JohnFoster (talk | contribs) (Revisions to Introduction and Software & Installation sections) |
||
Line 1: | Line 1: | ||
== Disclaimer == |
== Disclaimer == |
||
− | This tutorial is intended for those running QGIS on the Windows 10 operating system and it assumes a basic knowledge of GIS environments and Python syntax. Originally authored by Michael Wray in 2019, the current version of this tutorial includes revisions made by John Foster in September, 2021. Please see the page history for the changelog. |
+ | This tutorial is intended for those running QGIS on the Windows 10 operating system and it assumes a basic knowledge of GIS environments and Python syntax. Originally authored by Michael Wray in 2019, the current version of this tutorial includes revisions made by John Foster in September, 2021. Please see the page history for the changelog. It's likely that this tutorial will remain relevant to future versions of QGIS but please be aware that some of the steps may become outdated in the coming years. |
− | The following |
+ | The following versions were used: |
* Windows 10 |
* Windows 10 |
||
− | * QGIS 3.16.11-Hannover LTR ( |
+ | * QGIS 3.16.11-Hannover LTR (OSGeo4W Network Installer) |
⚫ | |||
⚫ | |||
+ | * Python 3.9.5 (OSGeo4W Network Installer) |
||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
− | * Python 3.9.5 |
||
+ | |||
== Introduction == |
== Introduction == |
||
− | [https://qgis.org/en/site/ QGIS] is a free and open source [https://en.wikipedia.org/wiki/QGIS geographic information system (GIS)] that is developed and supported by thousands of users and organizations around the world. It's core features include a suite of vector and raster tools that are compatible with many of the most popular geospatial file formats. Additionally, [https://en.wikipedia.org/wiki/GDAL GDAL], [https://en.wikipedia.org/wiki/GRASS_GIS GRASS], and [https://en.wikipedia.org/wiki/SAGA_GIS SAGA] integration leverages the functionality of these powerful packages to make QGIS one of the most complete GIS environments available. Users who wish to expand upon this already extensive default toolset can search through the [https://plugins.qgis.org/plugins/ official QGIS plugin repository] (1508 plugins at the time of writing) to see if any published 3rd-party tools will meet their analysis needs. Written in the Python or C++ languages, QGIS plugins can be kept |
+ | [https://qgis.org/en/site/ QGIS] is a free and open source [https://en.wikipedia.org/wiki/QGIS geographic information system (GIS)] that is developed and supported by thousands of users and organizations around the world. It's core features include a suite of vector and raster tools that are compatible with many of the most popular geospatial file formats. Additionally, [https://en.wikipedia.org/wiki/GDAL GDAL], [https://en.wikipedia.org/wiki/GRASS_GIS GRASS], and [https://en.wikipedia.org/wiki/SAGA_GIS SAGA] integration leverages the functionality of these powerful packages to make QGIS one of the most complete GIS environments available. Users who wish to expand upon this already extensive default toolset can search through the [https://plugins.qgis.org/plugins/ official QGIS plugin repository] (1508 plugins at the time of writing) to see if any published 3rd-party tools will meet their analysis needs. Written in the Python or C++ languages, QGIS plugins can be kept in a private repository, or contributed to QGIS' public one. |
In this tutorial we will work through the process of creating a simple Python plugin for QGIS called DisplayInfo. While the functionality of the plugin itself is quite limited (it prints a layer's file path, extent, and coordinate reference system), following the steps in this guide should allow us to develop more advanced Python plugins as needed. |
In this tutorial we will work through the process of creating a simple Python plugin for QGIS called DisplayInfo. While the functionality of the plugin itself is quite limited (it prints a layer's file path, extent, and coordinate reference system), following the steps in this guide should allow us to develop more advanced Python plugins as needed. |
||
+ | |||
== Software & Installation == |
== Software & Installation == |
||
=== QGIS === |
=== QGIS === |
||
− | QGIS can be installed as a standalone application or through the OSGeo4W Network Installer. |
+ | QGIS can be installed as a standalone application or through the OSGeo4W Network Installer. Please refer to the following page for instructions: |
⚫ | |||
− | |||
⚫ | |||
− | |||
− | '''NOTE:''' QT designer is utilized as well, but it comes prepackaged with QGIS already. |
||
=== QT Designer === |
=== QT Designer === |
||
⚫ | |||
− | |||
⚫ | |||
* Standalone installation: |
* Standalone installation: |
||
Line 41: | Line 39: | ||
=== Text Editor === |
=== Text Editor === |
||
− | 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 |
+ | 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: [https://code.visualstudio.com/download Download Visual Studio Code.] |
Line 51: | Line 49: | ||
+ | Installation: |
||
− | To install these plugins: |
||
# Open QGIS and navigate to '''Plugins > Manage and Install Plugins...''' |
# Open QGIS and navigate to '''Plugins > Manage and Install Plugins...''' |
||
# Select the '''All''' tab to search the plugin repository |
# Select the '''All''' tab to search the plugin repository |
||
Line 58: | Line 56: | ||
Once installed they should both be immediately accessible through the '''Plugins''' menu. |
Once installed they should both be immediately accessible through the '''Plugins''' menu. |
||
+ | |||
== Methods == |
== Methods == |
Revision as of 12:09, 26 September 2021
Contents
Disclaimer
This tutorial is intended for those running QGIS on the Windows 10 operating system and it assumes a basic knowledge of GIS environments and Python syntax. Originally authored by Michael Wray in 2019, the current version of this tutorial includes revisions made by John Foster in September, 2021. Please see the page history for the changelog. It's likely that this tutorial will remain relevant to future versions of QGIS but please be aware that some of the steps may become outdated in the coming years.
The following versions were used:
- Windows 10
- QGIS 3.16.11-Hannover LTR (OSGeo4W Network Installer)
- QT Designer 5.15.2 (OSGeo4W Network Installer)
- Python 3.9.5 (OSGeo4W Network Installer)
- Plugin Builder 3.2.1 (QGIS Plugin)
- Plugin Reloader 0.8.2 (QGIS Plugin)
Introduction
QGIS is a free and open source geographic information system (GIS) that is developed and supported by thousands of users and organizations around the world. It's core features include a suite of vector and raster tools that are compatible with many of the most popular geospatial file formats. Additionally, GDAL, GRASS, and SAGA integration leverages the functionality of these powerful packages to make QGIS one of the most complete GIS environments available. Users who wish to expand upon this already extensive default toolset can search through the official QGIS plugin repository (1508 plugins at the time of writing) to see if any published 3rd-party tools will meet their analysis needs. Written in the Python or C++ languages, QGIS plugins can be kept in a private repository, or contributed to QGIS' public one.
In this tutorial we will work through the process of creating a simple Python plugin for QGIS called DisplayInfo. While the functionality of the plugin itself is quite limited (it prints a layer's file path, extent, and coordinate reference system), following the steps in this guide should allow us to develop more advanced Python plugins as needed.
Software & Installation
QGIS
QGIS can be installed as a standalone application or through the OSGeo4W Network Installer. Please refer to the following page for instructions:
QT Designer
QGIS was developed using the QT toolkit to create its graphical user interfaces. QT Designer is an application that is used to design these interfaces and comes packaged with QGIS. Depending on how you installed QGIS you can find QT Designer in the following locations:
- Standalone installation:
C:\Program Files\QGIS 3.16.11\apps\qt5\bin\designer.exe
- OSGeo4W installation:
C:\OSGeo4W\apps\Qt5\bin\designer.exe
Text Editor
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: Download Visual Studio Code.
Plugin Builder & Plugin Reloader
This tutorial requires two QGIS plugins:
- Plugin Builder: Generates our plugin files and boilerplate code
- Plugin Reloader: Removes the need to restart QGIS every time the plugin's code is changed
Installation:
- Open QGIS and navigate to Plugins > Manage and Install Plugins...
- Select the All tab to search the plugin repository
- Search for and install Plugin Builder 3 and Plugin Reloaded
Once installed they should both be immediately accessible through the Plugins menu.
Methods
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. 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. At the end of the dialogs you may receive a pyrcc5 error, but you can just ignore this.
Figure 2: Plugin builder input forms.
Figure 3: Plugin builder final dialog prompt.
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.
Figure 4: compile.bat file copied to plugin folder.
Figure 5: Opening the Active User Profile
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 and a new menu entry under the Plugins, both can be seen in Figure 7.
Figure 7: Plugin icon in toolbar and in menu.
Now if we click on the plugin (Figure 8), you will see a UI pops up ,but nothing is on it and it has no functionality. This is because we now need to edit the UI and add some logic to the plugin.
Figure 8: Default boilerplate UI.
Create the Plugin UI
Next we need to design a UI, first open QT Designer (or whatever form of QT creator you have from QGIS) which comes prepackaged with QGIS V 3.4.3, by typing in the search bar 'QT' and QT designer should show up. As this is a tutorial on how to create a plugin I'm not an expert on QT designer, I'd recommend going here for some in depth instructions on how to use it specifically for QGIS (this is the site I used to learn). After opening QT designer, go to File > Open File. Browse to the plugin subfolder of the user profile folder that you were shown how to locate here. Next, click on the Whatever you named your plugin.ui file, as shown in Figure 9.
Figure 9: .ui file for designing ui.
After opening the plugins .ui file and designing an appropriate UI for plugin (It's structure and look will vary, depending on what you want the plugin to do. Think: Do you need 1 input or 2, should the user be able to specify output?), save the file as. The completed UI for the DisplayInfo tool can be seen below in Figure 10.
Figure 10: Completed UI for the DisplayInfo plugin.
Adding the Code
The full code to replicate the DisplayInfo example plugin is viewable below, and only has to be copied into the .py file for the applicable plugin folder. For help with the Python required to make your own tool or just to get some new ideas, I highly suggest this site. Once, you've copied the DisplayInfo code below into the python > plugin your good to go. Use the Plugin Reloader to reload the DisplayInfo plugin, and run it.
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 : *********** ***************************************************************************/ /*************************************************************************** * * * 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
Below is a testcase for the plugin, a random layer is selected and the desired output location/name of the .txt file to be created is set, as shown in Figures 11-12.
Figure 11: DisplayInfo plugin with UI filled out.
Figure 12: Final output from DisplayInfo plugin after running.
References
1: https://gis-ops.com/qgis-simple-plugin/
2: https://gis-ops.com/qgis-3-qt-designer-explained/
3: https://gis-ops.com/qgis-3-plugin-development-reference-guide/