jueves, 6 de julio de 2017

Clase personalizada en PyQGIS para generar tooltips con values de ráster y registros de tablas atributivas de vectoriales

Con base en esta respuesta de gis.stackexchange.com, modifiqué la clase para incorporar como funcionalidades adicionales la posibilidad de visualizar e imprimir no sólo los values de los ráster sino también los atributos de vectoriales tipo punto, línea o polígono. El código completo se encuentra a continuación.


  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
from qgis.core import QgsRasterLayer, QgsRaster
from qgis.gui import QgsMapToolEmitPoint
from PyQt4.QtCore import QTimer
from PyQt4.QtGui import QToolTip
import itertools
import numpy as np

class TooltipLayerMapTool(QgsMapToolEmitPoint):
    def __init__(self, canvas):
        self.canvas = canvas
        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())
        print '(' + str(point[0]) + ', ' + str(point[1]) + ')'
        
        if self.canvas.underMouse():
            rLayer = iface.activeLayer()
            if type(rLayer) is QgsRasterLayer:
                ident = rLayer.dataProvider().identify( self.toMapCoordinates(self.canvas.mouseLastXY()), QgsRaster.IdentifyFormatValue )
                if ident.isValid():
                    text = ", ".join(['{0:g}'.format(r) for r in ident.results().values() if r is not None] )
                else:
                    text = "Non valid value"
                print text

            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() == QGis.Polygon:
                
                    for feat in feats:
                        if QgsGeometry.fromPoint(point).within(feat.geometry()):
                            text = ", ".join(['{}: {}'.format(names[i], r) for i, r in enumerate(feat.attributes()) if r is not None] )
                            break
                else:
                    n = len(feats)

                    distances = [ [] for i in range(n) ]
                    indices = []

                    for i, feat in enumerate(feats):
                        
                        dist = feat.geometry().distance(QgsGeometry.fromPoint(point))
                    
                        if dist < 500:
                            distances[i].append(dist)
                            indices.append(i)
                    
                    new_dist = []
                    new_indx = []

                    for j, group in enumerate(distances):
                        if group:
                            new_dist.append(group[0])
                            new_indx.append(j)
                    try:
                        j = new_indx[new_dist.index(np.min(new_dist))]
                    except ValueError:
                        pass
    
                    i = len(names)
                    text = ", ".join(['{}: {}'.format(names[i],r) for i, r in enumerate(feats[j].attributes()) if r is not None] )
            try:
                print text
            except UnboundLocalError:
                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 showMapTip( self ):
        self.timerMapTips.stop()
        if self.canvas.underMouse():
            rLayer = iface.activeLayer()
            if type(rLayer) is QgsRasterLayer:
                ident = rLayer.dataProvider().identify( self.toMapCoordinates(self.canvas.mouseLastXY()), QgsRaster.IdentifyFormatValue )
                if ident.isValid():
                    text = ", ".join(['{0:g}'.format(r) for r in ident.results().values() if r is not None] )
                else:
                    text = "Non valid value"
                QToolTip.showText( self.canvas.mapToGlobal( self.canvas.mouseLastXY() ), text, self.canvas )
            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() == QGis.Polygon:
                
                    for feat in feats:
                        if QgsGeometry.fromPoint(point).within(feat.geometry()):
                            text = ", ".join(['{}: {}'.format(names[i], r) for i, r in enumerate(feat.attributes()) if r is not None] )
                            break
                
                else:
                    n = len(feats)

                    distances = [ [] for i in range(n) ]
                    indices = []

                    for i, feat in enumerate(feats):
                        
                        dist = feat.geometry().distance(QgsGeometry.fromPoint(point))
                    
                        if dist < 500:
                            distances[i].append(dist)
                            indices.append(i)
                    
                    new_dist = []
                    new_indx = []

                    for j, group in enumerate(distances):
                        if group:
                            new_dist.append(group[0])
                            new_indx.append(j)
                    try:
                        j = new_indx[new_dist.index(np.min(new_dist))]
                    except ValueError:
                        pass
    
                    i = len(names)
                    text = ", ".join(['{}: {}'.format(names[i],r) for i, r in enumerate(feats[j].attributes()) if r is not None] )
            try:
                QToolTip.showText( self.canvas.mapToGlobal( self.canvas.mouseLastXY() ), text, self.canvas )
            except UnboundLocalError:
                pass

tooltipRaster = TooltipLayerMapTool( iface.mapCanvas() )
iface.mapCanvas().setMapTool( tooltipRaster ) # Use your Map Tool!

Después de ejecutado en la Python Console de QGIS, al posicionar la cruz del cursor sobre cualquier rasgo del vectorial aparecerá la tooltip; tal como se presenta en la imagen siguiente:


Si se hace click con el mouse del ratón, los valores de la tooltip, conjuntamente con las coordenadas del punto, aparecerán impresos en la Python Console. Esto se visualiza a continuación:


Para los que todavía no han captado una posible utilidad de esto, imaginen lo fácil que sería aprender geografía empleando el shapefile world_borders; tal como se señala a continuación con el continente africano (cuántos sabran que el país señalado en la imagen es Zaire?):





No hay comentarios: