¿Es posible cambiar los símbolos de leyenda en la leyenda de QGIS print composer?

9

Tengo algunas capas con símbolos muy complejos definidos por datos. Si uso una leyenda en el compositor de impresión, estos símbolos no se dibujan correctamente.

¿Hay alguna manera de cambiar los símbolos de leyenda con pyqgis, para que pueda usar imágenes PNG o SVG personalizadas en lugar del símbolo de leyenda predeterminado para estas capas?

Sé cómo agregar un botón al compositor de impresión y cómo conectarlo con una función. Quiero agregar un botón a la configuración de leyenda para permitirme reemplazar el icono de leyenda generado automáticamente con una imagen personalizada. Entonces, lo que aún necesito es la información de cómo puedo acceder a los símbolos de leyenda con pyqgis / pyqt, y cómo reemplazarlos con una QImage en un QLabel o algo así.

Maqueta muy básica del botón utilizado para cambiar el símbolo:

ingrese la descripción de la imagen aquí

Leyenda generada automáticamente:

ingrese la descripción de la imagen aquí

Leyenda con símbolo de leyenda personalizado:

ingrese la descripción de la imagen aquí

Ya descubrí cómo acceder a los elementos de la leyenda en el compositor de impresión, pero aún no cómo acceder al símbolo en sí:

import qgis
from PyQt4.QtCore import *
from PyQt4.QtGui import *

activeComposer = iface.activeComposers()

for item in activeComposer:
    if item.composerWindow().windowTitle()=='test':
        for i in item.items():
            if isinstance(i,QgsComposerLegend):
                #print i
                #print i.model()
                legend = i
                for i in xrange(legend.modelV2().rowCount()):
                    posteleg=legend.modelV2().index(i, 0)
                    print posteleg.data()

Editar 1:

También descubrí cómo acceder a los objetos QIcon del árbol de leyendas, pero todavía no puedo intercambiarlos:

def run(self):

        activeComposer = self.iface.activeComposers()
        #print(self.resolve('icon.png'))
        for item in activeComposer:
            if item.composerWindow().windowTitle()=='test':
                for i in item.items():
                    if isinstance(i,QgsComposerLegend):
                        legend = i

                        layerIcon = QIcon(os.path.join(os.path.dirname(__file__), "icon.png"))

                        for i in xrange(legend.modelV2().rowCount()):
                            posteleg=legend.modelV2().index(i, 0)
                            posteleg.model().iconGroup().swap(layerIcon)
                            print posteleg.data()

Aquí hay un ejemplo de la vida real donde puede ver una simbología combinada de muchas capas de símbolos: ingrese la descripción de la imagen aquí Esto terminará en la leyenda de esta manera: ingrese la descripción de la imagen aquí

Como necesito el símbolo apropiado en la leyenda, me gustaría hacer una captura de pantalla de mi símbolo, recortarlo y usarlo como una imagen en mi leyenda.

Sé que podría superponer una imagen separada en la parte superior de mi leyenda que cubre el símbolo generado automáticamente, pero me gustaría tener una solución "más limpia" que me permita reemplazar los símbolos en la leyenda con imágenes personalizadas.

Edición 2:

Mientras tanto, he encontrado otra forma de obtener acceso a las entradas de la leyenda:

from qgis.core import QgsLegendRenderer, QgsComposerLegendStyle

compDict = {}
for comp in iface.activeComposers():
    # workaround to get name: read it from window title
    compDict[comp.composerWindow().windowTitle()] = comp.composition()
if "mycomposername" in compDict:
    itemLlegend = compDict["mycomposername"].getComposerItemById("mylegend_id")
    if itemLlegend:
        print itemLlegend

tree_layer_layer =  itemLlegend.modelV2().rootGroup().children()
for item in tree_layer_layer:
        if item.layerName()=="MyLayername":
            print "match"
            QgsLegendRenderer.setNodeLegendStyle(item, QgsComposerLegendStyle.Group)

Esto me permite acceder a los objetos QgsLayerTreeLayer y puedo cambiar el estilo de la leyenda (Grupo, Subgrupo, Oculto). Pero todavía no tengo idea de cómo acceder al símbolo de la leyenda. ¿Algunas ideas?

Markgraeflerland
fuente
1
Cuando me encuentre con esto con QGIS, normalmente crearé capas adicionales que tienen la simbología que quiero mostrar en la leyenda (generalmente duplicados simples de capas existentes, no nuevas fuentes de datos). Luego, en el compositor configuré la ventana del mapa y bloqueé las capas. Una vez que las capas están bloqueadas, enciendo las capas "falsas" y puedo agregarlas a una leyenda. Es puramente una solución alternativa, y no en PyQGIS, pero ¿quizás haya alguna forma de emular las capas "falsas" para lo que necesita?
Nate Wanner

Respuestas:

10

Como este tema cubre muchos argumentos, solo me enfocaré en las capas de símbolos SVG, esperando haber entendido bien lo que está buscando (no me di cuenta de la longitud de la respuesta mientras escribía, así que lo siento por eso, pero espero que agregue más claridad, sin embargo).


Contexto

1) Clases de capa de símbolo

Las siguientes clases de capa de símbolo están disponibles para el formato SVG:

  • QgsSvgMarkerSymbolLayerV2 , que muestra una geometría de punto utilizando una imagen SVG especificada (funciona para capas de puntos );
  • QgsSVGFillSymbolLayer , que dibuja el interior de una geometría de polígono utilizando una imagen SVG repetida (funciona para capas de polígono ).

Un enfoque común para crear una capa de símbolo es inicializarla con un diccionario de propiedades.

Puede inicializar una nueva capa de símbolo y ver sus propiedades predeterminadas de esta manera:

symbol_layer = QgsSvgMarkerSymbolLayerV2()
for k,v in symbol_layer.properties().iteritems():
    print k, ':', v

Obtendrá todas las propiedades que están almacenadas en él:

outline_width : 0.2
outline_color : 0,0,0,255
angle : 0
name : crosses/Star1.svg
scale_method : diameter
color : 0,0,0,255
size_unit : MM
horizontal_anchor_point : 1
size_map_unit_scale : 0,0,0,0,0,0
outline_width_unit : MM
offset : 0,0
offset_map_unit_scale : 0,0,0,0,0,0
outline_width_map_unit_scale : 0,0,0,0,0,0
size : 4
vertical_anchor_point : 1
offset_unit : MM

Si desea editar las propiedades, puede usar métodos, que pueden llamarse desde la ayuda de la clase (por ejemplo, ejecutar help(QgsSvgMarkerSymbolLayerV2)en la Consola Python). Verá más adelante un ejemplo de cómo usar métodos.

En aras de la exhaustividad, también puede inicializar una capa de símbolo con un diccionario de propiedades (por ejemplo, ver aquí ), pero sinceramente prefiero el primer enfoque y lo usaré.

2) Crear un renderizador

Para usar la capa de símbolo una vez que la ha creado (y finalmente editado), debe crear un renderizador apropiado y luego asignar ese renderizador a su capa de mapa.

Para acceder al renderizador existente de una capa:

renderer = layer.rendererV2()

Para obtener una lista de los tipos de renderizadores disponibles, use:

renderer_types = QgsRendererV2Registry().renderersList()

Para su caso, debemos tratar con un renderizador de símbolos categorizados . Como dije antes, necesitas crear un renderizador y luego asignarlo a la capa:

# define the lookup: value -> (color, label)
landuses = {'Agriculture': ('#d3a151', 'Agriculture'), 'Natural': ('#175dcd', 'Natural'),}

# create a category for each item in landuses
categories = []
for landuse_name, (color, label) in landuses.items():
    symbol = QgsSymbolV2.defaultSymbol(layer.geometryType())
    symbol.setColor(QColor(color))
    category = QgsRendererCategoryV2(landuse_name, symbol, label)
    categories.append(category)

# create the renderer and assign it to the layer
expression = 'landuse' # field name
renderer = QgsCategorizedSymbolRendererV2(expression, categories) # categorized symbol renderer
layer.setRendererV2(renderer) # assign the renderer to the layer

3) Cambiar la capa de símbolo

Los varios símbolos del renderizador de símbolos categorizados se pueden llamar symbols()(devuelve una lista):

for symb in renderer.symbols():
    print symb

<qgis._core.QgsMarkerSymbolV2 object at 0x0E1FF760>
<qgis._core.QgsMarkerSymbolV2 object at 0x0E1FF7B0>

Si desea reemplazar una capa de símbolo dada dentro de la symboldefinida anteriormente, solo necesita conocer su índice y luego decirle al procesador de esta manera:

renderer.symbols()[0].changeSymbolLayer(0, new_symbol)

donde [0]indica el primer elemento del grupo categorizado.

Solución

Finalmente, ¡apliquemos lo que acabamos de aprender!

Asumiendo trabajar en esta capa de polígono, que almacena los usos del suelo que hemos definido antes:

ingrese la descripción de la imagen aquí

Si desea cambiar el patrón predeterminado para los usos de la tierra agrícola (tienen la posición No. 1 en el grupo 'landuse') con una imagen SVG específica, puede ejecutar este código (lea aquí para saber cómo agregar un SVG personalizado) camino):

import qgis
from PyQt4.QtCore import *
from PyQt4.QtGui import *

# define the lookup: value : (color, label)
landuses = {'Agriculture': ('#d3a151', 'Agriculture'), 'Natural': ('#175dcd', 'Natural'),}

# create a category for each item in landuses
categories = []
for landuse_name, (color, label) in landuses.items():
    symbol = QgsSymbolV2.defaultSymbol(layer.geometryType())
    symbol.setColor(QColor(color))
    category = QgsRendererCategoryV2(landuse_name, symbol, label)
    categories.append(category)

# create the renderer and assign it to the layer
expression = 'landuse' # field name
renderer = QgsCategorizedSymbolRendererV2(expression, categories)

activeComposer = iface.activeComposers()
for item in activeComposer:
    if item.composerWindow().windowTitle()=='test':
        for i in item.items():
            if isinstance(i,QgsComposerLegend):
                legend = i
                for k in xrange(legend.modelV2().rowCount()):
                    posteleg=legend.modelV2().index(k, 0)
                    layer = QgsMapLayerRegistry.instance().mapLayersByName( posteleg.data() )[0]
                    if k == 0: # k is the position of the layer of interest in Legend (NOT in the Layers Panel)
                        svg_location = 'C:/path_to_svg/smile.svg'
                        new_symbol = QgsSVGFillSymbolLayer()
                        new_symbol.setSvgFilePath(svg_location)
                        new_symbol.setPatternWidth(7.0)
                        #... If you want to set additional parameters, type help(QgsSVGFillSymbolLayer) in the Python Console
                        renderer.symbols()[1].changeSymbolLayer(0, new_symbol)
                    layer.setRendererV2(renderer)
                    layer.triggerRepaint()

Este será el resultado:

ingrese la descripción de la imagen aquí

El código anterior es muy aproximado, pero como no sabía si deseaba una solución específica para su ejemplo o una explicación más general, preferí centrar la atención en las herramientas disponibles en lugar de refinar el código en sí (estoy seguro ¡puede editarlo ligeramente de acuerdo con sus necesidades específicas!).

mgri
fuente
Gracias por su extensa respuesta, pero estoy buscando una solución para cambiar el símbolo (solo) en la leyenda del compositor, no para la capa en sí. Actualizaré mi pregunta anterior en un segundo con un ejemplo más
markgraeflerland
@markgraeflerland Estoy seguro de que nadie pensó en el problema explicado en su pregunta editada (prácticamente nueva). Nunca especificó que la imagen en la leyenda no reproducirá lo que ve en la capa (parecía que quería reemplazar un elemento de la leyenda con una imagen, como lo he hecho). Sinceramente, en mi opinión, su pregunta original fue muy engañosa y mi publicación realmente trató de responderla. Lamento el tiempo perdido, pero no eliminaré la respuesta, ya que podría ser de interés para otra persona cuando busques en Google. ¡Buena suerte con tu investigación!
mgri
2
En la primera oración de mi pregunta escribí "Si uso una leyenda en el compositor de impresión, estos símbolos no se dibujan correctamente", así que no creo que no haya especificado que la imagen en la leyenda no reproducirá lo que yo ver en la capa
markgraeflerland
5

Alternativamente, y sin la codificación de Python, resolví esto creando un nuevo grupo de capas dedicado a la creación de leyendas, donde puedo poner lo que quiera, con los tamaños y colores que quiero. Así en el compositor de impresión, acabo de eliminar de los elementos de la leyenda las capas de datos reales y conservé solo el grupo de capas de la leyenda.

Es específicamente práctico cuando se necesita hacer una leyenda que pueda representar casos que no suceden en el mapa impreso real.

EDITAR: y sobre su segunda edición, con los símbolos compuestos que no se muestran correctamente, ¿tiene algunas variables que definan sus símbolos, como "C" o "G" en realidad es relativo a un campo o la orientación? Si es así, QGIS no puede adivinar qué desea que se muestre, por lo que muestra todo sin ningún valor para estos parámetros. Una solución alternativa puede ser guardar de manera simple el símbolo con algunos valores fijos en lugar de las variables. Esta manera me permitió reemplazar este elemento de leyenda de visualización predeterminado: ingrese la descripción de la imagen aquípor este que se ajusta a mi necesidadingrese la descripción de la imagen aquí

GuiOm Clair
fuente