Est-il possible de modifier les symboles de légende dans la légende du composeur d'impression QGIS?

9

J'ai quelques couches avec des symboles définis par les données très complexes. Si j'utilise une légende dans le compositeur d'impression, ces symboles ne sont pas dessinés correctement.

Existe-t-il un moyen de modifier les symboles de légende avec des pyqgis, donc je peux utiliser des images PNG ou SVG personnalisées au lieu du symbole de légende par défaut pour ces couches?

Je sais comment ajouter un bouton-poussoir au composeur d'impression et comment le connecter à une fonction. Je veux ajouter un bouton aux paramètres de légende pour me permettre de remplacer l'icône de légende générée automatiquement par une image personnalisée. Donc, ce dont j'ai encore besoin, c'est de savoir comment accéder aux symboles de légende avec pyqgis / pyqt, et comment les remplacer par un QImage sur un QLabel ou quelque chose comme ça?

Maquette très basique du bouton utilisé pour changer le symbole:

entrez la description de l'image ici

Légende générée automatiquement:

entrez la description de l'image ici

Légende avec symbole de légende personnalisé:

entrez la description de l'image ici

J'ai déjà découvert comment accéder aux éléments de la légende dans le compositeur d'impression mais pas encore comment accéder au symbole lui-même:

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()

Modifier 1:

J'ai également découvert comment accéder aux objets QIcon de l'arbre de légende, mais je ne peux pas encore les échanger:

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()

Voici un exemple réel où vous pouvez voir une symbologie combinée de plusieurs couches de symboles: entrez la description de l'image ici Cela se retrouvera dans la légende comme ceci: entrez la description de l'image ici

Comme j'ai besoin du bon symbole dans la légende, je voudrais faire une capture d'écran de mon symbole, le recadrer et l'utiliser comme image dans ma légende.

Je sais que je pourrais simplement superposer une image distincte au-dessus de ma légende qui couvre le symbole généré automatiquement, mais j'aimerais avoir une solution "plus propre" qui me permette de remplacer les symboles de la légende par des images personnalisées.

Modifier 2:

En attendant, j'ai trouvé un autre moyen d'accéder aux entrées de la légende:

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)

Cela me permet d'accéder aux objets QgsLayerTreeLayer et je peux changer le style de légende (Group, Subgroup, Hidden). Mais je ne sais toujours pas comment accéder au symbole de légende. Des idées?

Markgraeflerland
la source
1
Lorsque j'exécute cela avec QGIS, je crée généralement des couches supplémentaires qui ont la symbologie que je veux afficher dans la légende (généralement des doublons simples de couches existantes - pas de nouvelles sources de données). Ensuite, dans le compositeur, j'ai configuré la fenêtre de la carte et verrouillé les couches. Une fois les calques verrouillés, j'active les "faux" calques et je peux les ajouter à une légende. C'est purement une solution de contournement, et pas dans PyQGIS, mais peut-être existe-t-il un moyen d'émuler les "fausses" couches pour ce dont vous avez besoin?
Nate Wanner

Réponses:

10

Étant donné que ce sujet couvre de nombreux arguments, je ne me concentrerai que sur les couches de symboles SVG, en espérant avoir bien compris ce que vous recherchez (je n'ai pas réalisé la longueur de la réponse lors de l'écriture, donc je suis désolé pour mais j'espère que cela ajoutera plus de clarté).


Le contexte

1) Classes de couches de symboles

Les classes de couches de symboles suivantes sont disponibles pour le format SVG:

  • QgsSvgMarkerSymbolLayerV2 , qui affiche une géométrie de point à l'aide d'une image SVG spécifiée (cela fonctionne pour les couches de points );
  • QgsSVGFillSymbolLayer , qui dessine l'intérieur d'une géométrie de polygone à l'aide d'une image SVG répétée (cela fonctionne pour les couches de polygone ).

Une approche courante pour créer une couche de symboles consiste à l'initialiser avec un dictionnaire de propriétés.

Vous pouvez initialiser une nouvelle couche de symboles et voir ses propriétés par défaut de cette façon:

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

Vous obtiendrez toutes les propriétés qui y sont stockées:

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 vous souhaitez modifier les propriétés, vous pouvez utiliser des méthodes, qui peuvent être appelées à l'aide de la classe (par exemple exécutées help(QgsSvgMarkerSymbolLayerV2)dans la console Python). Vous verrez plus tard un exemple d'utilisation des méthodes.

Par souci d'exhaustivité, vous pouvez également initialiser une couche de symboles avec un dictionnaire de propriétés (par exemple, voir ici ), mais je préfère sincèrement la première approche et je vais l'utiliser.

2) Création d'un rendu

Pour utiliser la couche de symboles une fois que vous l'avez créée (et éventuellement modifiée), vous devez créer un rendu approprié, puis affecter ce rendu à votre couche de carte.

Pour accéder au rendu existant d'un calque:

renderer = layer.rendererV2()

Pour obtenir une liste des types de rendu disponibles, utilisez:

renderer_types = QgsRendererV2Registry().renderersList()

Pour votre cas, nous devons traiter avec un rendu de symbole catégorisé . Comme je l'ai déjà dit, vous devez créer un moteur de rendu, puis l'assigner au calque:

# 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) Changer la couche de symboles

Les différents symboles du moteur de rendu de symboles catégorisé sont appelables via symbols()(il renvoie une liste):

for symb in renderer.symbols():
    print symb

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

Si vous voulez remplacer une couche de symboles donnée dans la symboldéfinition précédente, il vous suffit de connaître son index et de le dire au rendu de cette manière:

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

[0]indique le premier élément du groupe catégorisé.

Solution

Enfin, appliquons ce que nous venons d'apprendre!

En supposant de travailler sur cette couche de polygones, qui stocke les utilisations du sol que nous avons définies auparavant:

entrez la description de l'image ici

Si vous souhaitez modifier le modèle par défaut pour les utilisations des terres agricoles (ils ont la position n ° 1 dans le groupe 'landuse') avec une image SVG spécifique, vous pouvez exécuter ce code (lisez ici pour savoir comment ajouter un SVG personnalisé chemin):

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()

Ce sera le résultat:

entrez la description de l'image ici

Le code ci-dessus est très approximatif mais, comme je ne savais pas si vous vouliez une solution spécifique pour votre exemple ou une explication plus générale, j'ai préféré concentrer l'attention sur les outils disponibles au lieu d'affiner le code lui-même (je suis sûr vous pouvez le modifier légèrement selon vos besoins spécifiques!).

mgri
la source
Merci pour votre réponse détaillée mais je cherche une solution pour changer le symbole (seulement) dans la légende du compositeur pas pour la couche elle-même. Je mettrai à jour ma question ci-dessus dans une seconde avec un autre exemple
markgraeflerland
@markgraeflerland Je suis sûr que personne n'a pensé au problème expliqué dans votre (pratiquement nouvelle) question éditée. Vous n'avez jamais spécifié que l'image dans la légende ne reproduirait pas ce que vous voyez dans le calque (il semblait que vous vouliez remplacer un élément de la légende par une image, comme je l'ai fait). Sincèrement, à mon avis, votre question initiale était très trompeuse et mon message a en fait essayé d'y répondre. Je suis désolé pour le temps perdu, mais je ne supprimerai pas la réponse car cela pourrait intéresser quelqu'un d'autre lors de la recherche sur Google. Bonne chance dans vos recherches!
mgri
2
Dans la première phrase de ma question, j'ai écrit "Si j'utilise une légende dans le compositeur d'impression, ces symboles ne sont pas dessinés correctement" donc je ne pense pas que je n'ai pas spécifié que l'image dans la légende ne reproduira pas ce que je voir dans la couche.
markgraeflerland
5

Alternativement, et sans codage Python, j'ai résolu cela en créant un nouveau groupe de calques dédié à la création de légendes, où je peux mettre tout ce que j'aime, avec les tailles et les couleurs que je veux. Comme ceci dans le compositeur d'impression, je viens de supprimer des éléments de légende les couches de données réelles et de ne conserver que le groupe de couches de légende.

Il est particulièrement pratique lorsque vous avez besoin de créer une légende qui peut représenter des cas qui ne se produisent pas sur la carte imprimée réelle.

EDIT: et à propos de votre deuxième édition, avec les symboles composés qui ne s'affichent pas correctement, avez-vous des variables définissant vos symboles, comme le "C" ou "G" est en fait relatif à un champ, ou l'orientation? Si c'est le cas, QGIS ne peut pas deviner ce que vous voulez afficher, il passe donc par tout afficher sans aucune valeur pour ces paramètres. Une solution de contournement peut consister à enregistrer le symbole avec quelques valeurs fixes à la place des variables. Cela m'a permis de remplacer cet élément de légende d'affichage par défaut: entrez la description de l'image icipar celui-ci répondant à mes besoinsentrez la description de l'image ici

GuiOm Clair
la source