lunes, 28 de diciembre de 2020

Adaptar un plugin plantilla tipo botón checkeable para producir rectángulos por dragado del Map Canvas en QGIS 3

En el post anterior se produjo un plugin plantilla tipo botón checkeable para PyQGIS 3. Ahora lo vamos a adaptar para producir rectángulos por dragado del Map Canvas; tal como se realizó en este otro post. A continuación, para tener una plantilla verdadera, se elimina en template.py todo el código que permitía generar las tooltips consideradas en el post anterior y se reserva para tal fin. El código final se presenta a continuación.

"""
/***************************************************************************
 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
from qgis.gui import QgsMapToolEmitPoint
from PyQt5.QtCore import QObject, QTimer, Qt
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 = TooltipRasterMapTool( 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" ), "Tooltip for Raster Values", 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) != TooltipRasterMapTool:
            self.action.setChecked( False )


class TooltipRasterMapTool(QgsMapToolEmitPoint):
    def __init__(self, iface):
        self.iface = iface
        self.canvas = self.iface.mapCanvas()
        QgsMapToolEmitPoint.__init__(self, self.canvas)

    def canvasPressEvent(self, e):
        pass

    def canvasReleaseEvent(self, e):
        pass

    def canvasMoveEvent(self, e):
        pass

El código total adaptado en template.py se presenta a continuación. 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, QgsPointXY, 
                       QgsGeometry, QgsRectangle, QgsProject, QgsVectorLayer, QgsFeature)
from qgis.gui import QgsMapToolEmitPoint, QgsRubberBand
from PyQt5.QtCore import QObject, QTimer, Qt
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 = TooltipRasterMapTool( 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" ), "For Making Rectangles", 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) != TooltipRasterMapTool:
            self.action.setChecked( False )

class TooltipRasterMapTool(QgsMapToolEmitPoint):
    def __init__(self, iface):
        self.iface = iface
        self.canvas = self.iface.mapCanvas()
        QgsMapToolEmitPoint.__init__(self, self.canvas)
        self.start_point = None
        self.end_point = None
        self.rubber_band = QgsRubberBand(self.canvas, True)
        self.rubber_band.setColor(Qt.red)
        self.rubber_band.setOpacity(0.7)

    def canvasPressEvent(self, e):
        self.start_point = self.toMapCoordinates(e.pos())
        self.end_point = self.start_point

    def canvasReleaseEvent(self, e):
        self.end_point = self.toMapCoordinates(e.pos())
        r = self.get_rectangle()
        self.rubber_band.reset()
        self.start_point = None
        self.end_point = None

        rect_feat = r.asWktPolygon()

        #definng Status Bar
        statusBar = self.iface.mainWindow().statusBar()
        #selecting elements in Status Bar
        elements = statusBar.children()
        #selecting widget in Status Bar with ESPG
        EPSG = elements[1].children()[10]
        EPSG = EPSG.text().split(':')

        uri = "Polygon?crs=epsg:" + EPSG[1] + "&field=id:integer""&index=yes"

        mem_layer = QgsVectorLayer(uri,
                                   'rectangle',
                                   'memory')

        prov = mem_layer.dataProvider()

        feat = QgsFeature()
        feat.setAttributes([0])
        feat.setGeometry(QgsGeometry.fromWkt(rect_feat))
        prov.addFeatures([feat])
        QgsProject.instance().addMapLayer(mem_layer)

    def get_rect_points(self, startPoint, endPoint):
        points = [[QgsPointXY(startPoint.x(), startPoint.y()), QgsPointXY(endPoint.x(), startPoint.y()), QgsPointXY(endPoint.x(), endPoint.y()), QgsPointXY(startPoint.x(), endPoint.y())]]
        return points

    def get_rectangle(self): 
        rect = QgsRectangle(self.start_point, self.end_point)
        return rect

    def canvasMoveEvent(self, e):
 
        if self.start_point:
            end_point = self.toMapCoordinates(e.pos())
            self.rubber_band.reset()
            p = self.get_rect_points(self.start_point, end_point)
            self.rubber_band.setToGeometry(QgsGeometry.fromPolygonXY(p), None)
        

La carpeta completa de la plantilla (template) se guarda con el nombre de makingrectangle en la ruta de plugins de QGIS 3. El archivo template.py que tiene el código anterior se renombra como making_rectangles.py. Para que lo procese de la manera esperada se hace la modificación siguiente en el __init__py.

.
.
.
def classFactory(iface):
    # load Test class from file test.py
    from .making_rectangles 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 al inicio del método 'initGui'.

; the next section is mandatory
[general]
name=Making Rectangles plugin
description=For Making Rectangles.
.
.

Finalmente, se produce un icono personalizado (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: