jueves, 31 de diciembre de 2020

Adaptar un plugin plantilla tipo botón checkeable para desplegar atributos y áreas de features (capas vectoriales) como tooltip en QGIS 3

En un post anterior se adaptó un plugin, de QGIS 2 a QGIS 3, para desplegar ráster values como tooltip. En este post se va a adaptar ese comportamiento para desplegar los valores de todos los atributos y el área (en hectáreas) de capas vectoriales. El código completo se expone a continuación y se copió en el template.py; renombrado posteriormente como measure_area.py en la carpeta measurearea. Sin embargo, para que funcione adecuadamente se requieren otras modificaciones que se detallan más adelante.

"""
/***************************************************************************
 Test
                                 A QGIS plugin
 Plugin for testing things
                              -------------------
        begin                : 2015-01-28
        copyright            : (C) 2015-2017 by German Carrillo, GeoTux
        email                : gcarrillo@linuxmail.org
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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 the PyQt and QGIS libraries
from qgis.core import QgsApplication, QgsRasterLayer, QgsRaster, QgsVectorLayer, Qgis, QgsGeometry, QgsWkbTypes
from qgis.gui import QgsMapToolEmitPoint
from PyQt5.QtCore import QObject, QTimer
from PyQt5.QtWidgets import ( QMessageBox, QAction, QMenu, QActionGroup,
                              QWidgetAction, QToolTip)
from PyQt5.QtGui import ( QIcon )

class Test:

    def __init__(self, iface):
        # Save reference to the QGIS interface
        self.iface = iface
        self.tooltipRaster = TooltipAreaPolygonMapTool( self.iface )

    def initGui(self):
        # Create action that will start plugin configuration
        self.action = QAction( QIcon( os.path.dirname(os.path.realpath(__file__)) +
            "/icon_default.png" ), "Measure Feature Areas", self.iface.mainWindow() )
        # connect the action to the run method
        self.action.triggered.connect( self.run )
        self.action.setCheckable( True )

        self.iface.mapCanvas().mapToolSet.connect( self.mapToolWasSet )

        # Add toolbar button to the Plugins toolbar
        self.iface.addToolBarIcon(self.action)
        # Add menu item to the Plugins menu
        self.iface.addPluginToMenu("&Tooltip for Raster Values", self.action)

    def unload(self):
        # Remove the plugin menu item and icon
        self.iface.removePluginMenu( "&Tooltip for Raster Values", self.action )
        self.iface.removeToolBarIcon(self.action)

    # run method that performs all the real work
    def run(self, checked):
        if checked:
            self.iface.mapCanvas().setMapTool( self.tooltipRaster )
        else:
            self.iface.mapCanvas().unsetMapTool( self.tooltipRaster )

    def mapToolWasSet( self, newTool ):
        if type(newTool) != TooltipAreaPolygonMapTool:
            self.action.setChecked( False )


class TooltipAreaPolygonMapTool(QgsMapToolEmitPoint):
    def __init__(self, iface):
        self.iface = iface
        self.canvas = self.iface.mapCanvas()
        QgsMapToolEmitPoint.__init__(self, self.canvas)
        self.timerMapTips = QTimer( self.canvas )
        self.timerMapTips.timeout.connect(self.showMapTip)

    def canvasPressEvent(self, e):
        point = self.toMapCoordinates(self.canvas.mouseLastXY())
        
        if self.canvas.underMouse():
            rLayer = self.iface.activeLayer()

            if type(rLayer) is QgsVectorLayer:
                feats_ids = rLayer.fields().allAttributesList()
                names = [ rLayer.fields().field(id).name() for id in feats_ids ]
                
                feats = [ feat for feat in rLayer.getFeatures() ] 
                point = self.toMapCoordinates(self.canvas.mouseLastXY())
                
                text = "no layer features"
                
                if rLayer.geometryType() == QgsWkbTypes.PolygonGeometry:
                    
                    for feat in feats:
                        if QgsGeometry.fromPointXY(point).within(feat.geometry()):
                            text = ", ".join(['{}: {}'.format(names[i], r) for i, r in enumerate(feat.attributes()) if r is not None] )
                            break
                
                else:
                    pass

    def canvasReleaseEvent(self, e):
        pass

    def canvasMoveEvent(self, e):
        if self.canvas.underMouse(): # Only if mouse is over the map
            QToolTip.hideText()
            self.timerMapTips.start( 700 ) # time in milliseconds

    def deactivate(self):
        self.timerMapTips.stop()

    def showMapTip( self ):
        self.timerMapTips.stop()
        if self.canvas.underMouse():
            rLayer = self.iface.activeLayer()

            if type(rLayer) is QgsVectorLayer:
                feats_ids = rLayer.fields().allAttributesList()
                names = [ rLayer.fields().field(id).name() for id in feats_ids ]
                
                feats = [ feat for feat in rLayer.getFeatures() ] 
                point = self.toMapCoordinates(self.canvas.mouseLastXY())
                
                text = "no layer features"
                
                if rLayer.geometryType() == QgsWkbTypes.PolygonGeometry:
                
                    for feat in feats:
                        if QgsGeometry.fromPointXY(point).within(feat.geometry()):
                            text = ", ".join(['{}: {}'.format(names[i], r) for i, r in enumerate(feat.attributes()) if r is not None] )+ '\narea: ' + str(feat.geometry().area()/10000) + ' ha'
                            break
            
            try:
                QToolTip.showText( self.canvas.mapToGlobal( self.canvas.mouseLastXY() ), text, self.canvas )
            except UnboundLocalError:
                pass

La carpeta measurearea se encuentra en la ruta de plugins de QGIS 3. El archivo measure_area.py, que tiene el código anterior, para que se procese de la manera esperada es necesaria la modificación siguiente en el __init__py.

.
.
.
def classFactory(iface):
    # load Test class from file test.py
    from .measure_area import Test
    return Test(iface)

También necesitamos la modificación siguiente en el metadata.txt para que el nombre del plugin sea procesado como se espera. Una modificación similar también fue realizada en el código fuente (measure_area.py) al inicio del método 'initGui'.

; the next section is mandatory
[general]
name=Measure Areas plugin
description=For Measure Features Areas
.
.
.

Finalmente, se produce un icono personalizado 24x24 (con el mismo nombre) para identificarlo de manera precisa. La ejecución del plugin se observa en la imagen a continuación.

No hay comentarios: