Plugin Design

A QGIS plugin was created that generates an attribute table with a geometry describing column for the active vector layer in a QGIS workspace. The created geometry describing column is a mixture of two data types combining both geometry data values and geometry data types. Icons are used for geometry data types to create an intuitive language independent graphical interface.

The plugin is constructed with Python script using Python 3.x. By intention, current QGIS classes and methods are used as much as possible to reduce script complexity and to be resilient to new versions of QGIS. Initially the plugin was created with the QGIS plugin Builder Tool [26].

The main conceptual processes of the plugin are shown in Fig. 12 and are described in the following sections.

_images/PluginDesign.png

Fig. 12 The key processes in the Geometry Attribute Table plugin.

Geometry Describing Expression

The geometry describing expression can be accessed by the parseQGISGeometry module in the API, and, is shown below:

@qgsfunction(args='auto', group='Custom', usesGeometry=False)
def geometryField(feature, parent):
    #   THIS DOCUMENTATION IS NOT BE SHOWN BY THE SPHINX AUTODOC DIRECTIVE
    """
    Creates a QGIS expression called geometryField.  This expression
    returns a string that represents the geometry in the following
    order of decreasing precedence:

       * Null
       * Empty
       * Well known binary type string

    Returns:
       A string that represents the geometry.
    """
    geom = feature.geometry()
    #   Creates Point(0 0)
    emptyPoint1 = QgsPoint()
    #   Creates Point(nan nan)
    emptyPoint2 = emptyPoint1.createEmptyWithSameType()
    #   Null has to be tested for before empty as QGIS treats all null
    # geometries as empty
    if geom.isNull():
        return 'Null'
    elif geom.isEmpty():
        return 'Empty'
    #   Specific tests for empty points.
    elif geom.type().__eq__(0) and geom.vertexAt(0).__eq__(emptyPoint1):
        return 'Empty'
    elif geom.type().__eq__(0) and geom.vertexAt(0).__eq__(emptyPoint2):
        return 'Empty'
    else:
        return QgsWkbTypes.displayString(geom.wkbType())

This expression is performed per individual record (feature). This expression has a string data type output. A few characteristics of this expression are:

  1. it needs to test for null geometry values before empty geometry values;
  2. it has specific tests for empty point geometry values as these are given the set of vertices {0 0} or {nan nan} by QGIS depending on how they are instantiated;
  3. It reports the feature’s geometry data value when the geometry is empty or null, otherwise, it reports the feature’s geometry type; and,
  4. in the output, geometries with elevation (z values) or with measurements (m values) will still be distinguished from those without either.

This expression is used to populate a virtual field called “Geometry” that is appended to a vector dataset. A virtual field is a computed field that exists in memory only and is not written to the datasource. As the virtual field is appended to the vector layer, it may be viewed or used wherever a virtual field is present in QGIS. For example, when the window created by the plugin is open, the created “Geometry” field is included as a column in the standard Attribute Table.

Attribute Table Creation

The attribute table shown by the plugin is created by a process that is analogous to the standard QGIS Attribute Table. Basically, a cache of the dataset is made, and a model of the dataset is constructed so that Qt’s model-view [11] approach can be applied. When a piece of software outputs a table display of a data source, the software needs to replicate each data item and keep a synchronization between the original and the replication. Qt’s model-view reduces both the replicated dataset’s size and reduces the computing resources required for synchronization between the primary and replicated datasets. The following script is contained within the run(self) method that is within the geomAttribute class in the geomAttributeRun module of this plugin:

#   Create a cache of the vector layer data of size 10000
#   Cache geometry is true by default
#   For example: print(self.vector_layer_cache.cacheGeometry())
self.vector_layer_cache = QgsVectorLayerCache(self.layer, 10000)

#   QgsAttributeTableModel is a subclass of QAbstractTableModel
self.attribute_table_model = QgsAttributeTableModel(self.vector_layer_cache)

Within the same run(self) method of the geomAttribute class a filter is applied to the model of the dataset to cater for situations where selections are made:

#   The QgsAttributeTableFilterModel() is used to synchronize any selection.
self.attribute_table_filter_model = QgsAttributeTableFilterModel(
self.iface.mapCanvas(), self.attribute_table_model)

Putting Icons into the Attribute Table

Qt’s item delegate function is used to replace the string field in the virtual “Geometry” column in the plugin’s Attribute Table. This delegate function does not affect the same virtual field where it is used outside of the plugin’s Attribute Table. For example, the output of the same virtual field in the standard QGIS Attribute Table remains a string. Icon’s only replace strings for geometry types that contain the strings listed in Table 14 which are contained within a Python dictionary in the script. Any geometry type that doesn’t contain a string shown in Table 14 will remain as a string. For example, a string value of CircularString, CompoundCurve or GeometryCollection will remain unchanged and not be replaced by an icon. As the script searches for strings that contain the string: the string-switched-icon represents all variations of a geometry subtype including those that contain elevations or measurements; and, the order of searching and hence the order of dictionary keys is essential. For example, a search for Point would return True for MultiPoint as the string Point is contained within MultiPoint. So, MultiPoint needs to be searched for prior to Point.

Strings instead of icons are only used for empty and null geometry values with the application of background colors for several reasons.

For empty, there are several notations to represent an empty set including \(\{\ \}\) and \(\varnothing\). Neither of these notations for empty sets are widely known outside of mathematics and many QGIS users may not understand them without investigating. To aid interpretation, the string empty is kept in the Attribute Table with a background color of dark yellow. The use of tooltips for empty geometry data values is one possible approach in the future to use the empty set notation of \(\varnothing\) instead of the string empty.

One possible symbol for null is \(x\) which normally refers to an unknown variable, but, null refers to an unknown set of variables, not a single variable. Imieliński and Lipski proposed the use of “@” for null [19], however, this was not adopted by other researchers. As there is no universal symbol for null, the string null was used in combination with a background color of red.

Table 14 The strings that are replaced with icons by the item delegate function.
Precedence Geometry Type
1 MultiPoint
2 Point
3 MultiLine
4 Line
5 MultiPolygon
6 Polygon

The Python script that exchanges geometry type strings with icons, and, changes the background color of Empty and null geometry value strings is contained within the paint method in the myDelegate class of the geomAttributeRun module, as shown below:

    def paint(self, painter, option, index):
        """
        This method paints the geometry column attributes with icon's for
        display in the Plugin's Attribute-Table window.

        Args:
           option(QStyleOptionViewItem):
           painter(QPainter):
        """

        value = index.data(Qt.DisplayRole)

        iconDict = {
            "MultiPoint": "point_2x",
            "Point": 'point_1x',
            "MultiLine": 'line_2x',
            "Line": 'line_1x',
            "MultiPolygon": 'polygon_2x',
            "Polygon": 'polygon_1x'
        }

        #   Equivalent to "for iconKey in iconDict:"
        geometry = next(filter(value.__contains__, iconDict.keys()), None)
        if geometry is not None:
            icon = QIcon(':/plugins/geomAttribute/icons/{}.png'.format(iconDict.get(geometry)))
            icon.paint(painter, option.rect, Qt.AlignCenter)
        else:
            #   Set background color
            painter.setPen(QPen(Qt.NoPen))
            backgroundColor = Qt.lightGray
            geometry = next(filter(value.__eq__, ['Null', 'Empty']), None)
            if geometry is not None:
                if value.__eq__('Null'):
                    backgroundColor = Qt.red
                else:
                    backgroundColor = Qt.darkYellow
            painter.setBrush(backgroundColor)
            painter.drawRect(option.rect)
            #   Set text color - order is important.
            #   If done before background color will not show.
            painter.setPen(QPen(Qt.black))
            painter.drawText(option.rect, Qt.AlignCenter, value)

Plugin Window

The attribute table produced by the plugin is contained within a Qt Window. The preliminary plugin created by the QGIS Plugin Builder plugin instantiated a Qt Dialog. This Qt Dialog was exchanged with a Qt Window during the development of this plugin so that a tool bar with a help icon could be added to the window containing the attribute table. The Qt Window allows for further development of the plugin.

The plugin window is instantiated by the geomAttribute_window module:

class geomAttributeWindow(QtWidgets.QMainWindow, FORM_CLASS):
    def __init__(self, parent=None):
        """
        Constructor
        """
        super(geomAttributeWindow, self).__init__(parent)
        #   Set up the user interface that is constructed with Qt Designer.
        self.setupUi(self)

The plugin window design is in Qt’s designer file format (file extension .ui). With this approach, the window design can be changed independently from the plugin’s Python script using Qt Designer. To function, the “Table View” widget in Qt Designer had to be subclassed with the QgsAttributeTableView.

As Qt’s item delegate is used to replace strings in the produced attribute table with icons, all of the filenames for the icons had to be added to the Qt resource collection file (resources.qrc). The resources used by Qt have to be compiled whenever changes are made to the resource collection file.

The instantiation of the Geometry virtual field is coupled to the plugin’s window instantiation, such that this field is deleted when the plugin’s window is closed.

Plugin Help Files

The initial intention was to contain all of the plugin’s help files within the plugin so that they can be accessed offline. However, the construction of help files using the Qt framework was cumbersome. It was more efficient to produce HTML help files using Sphinx and have these hosted by the plugin’s GitHub site. An inspection of the help files for QGIS and the help files of many QGIS plugins revealed that most QGIS plugin help files are hosted on the world wide web and require an internet connection to access. Ironically, Sphinx can also produce Qt Help formatted files, and, this may be exploited in the future to generate offline help files.

Future Development

There is much scope to enhance the plugin. Some priorities for improvement are:

  1. add a summary table that reviews large datasets for null or empty geometry values;
  2. add another virtual field that displays the number of parts for multipart geometries;
  3. add icons for geometries with curved segments; and,
  4. add support for geometries with measurements or elevation.