Source code for safe_qgis.map_legend

"""
InaSAFE Disaster risk assessment tool developed by AusAid -
  **InaSAFE map legend module.**

Contact : ole.moller.nielsen@gmail.com

.. note:: 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.

"""
__author__ = 'tim@linfiniti.com'
__revision__ = '$Format:%H$'
__date__ = '10/01/2011'
__copyright__ = 'Copyright 2012, Australia Indonesia Facility for '
__copyright__ += 'Disaster Reduction'

import logging

from PyQt4 import QtCore, QtGui
from qgis.core import QgsMapLayer
from safe_qgis.exceptions import (LegendLayerException,
                                  KeywordNotFoundException)
from safe_qgis.utilities import qgisVersion
from safe_qgis.keyword_io import KeywordIO

LOGGER = logging.getLogger('InaSAFE')


[docs]class MapLegend(): """A class for creating a map legend.""" def __init__(self, theLayer): """Constructor for the Map Legend class. Args: theIface - reference to the QGIS iface object Returns: None Raises: Any exceptions raised will be propagated. """ LOGGER.debug('InaSAFE Map class initialised') self.legendImage = None self.layer = theLayer # how high each row of the legend should be self.legendIncrement = 30 self.keywordIO = KeywordIO() self.legendFontSize = 16 self.legendWidth = 500
[docs] def tr(self, theString): """We implement this ourself since we do not inherit QObject. Args: theString - string for translation. Returns: Translated version of theString. Raises: no exceptions explicitly raised. """ return QtCore.QCoreApplication.translate('MapLegend', theString)
[docs] def getLegend(self): """Examine the classes of the impact layer associated with this print job. .. note: This is a wrapper for the rasterLegend and vectorLegend methods. Args: None Returns: None Raises: An InvalidLegendLayer will be raised if a legend cannot be created from the layer. """ LOGGER.debug('InaSAFE Map Legend getLegend called') if self.layer is None: myMessage = self.tr('Unable to make a legend when map generator ' 'has no layer set.') raise LegendLayerException(myMessage) try: self.keywordIO.readKeywords(self.layer, 'impact_summary') except KeywordNotFoundException, e: myMessage = self.tr('This layer does not appear to be an impact ' 'layer. Try selecting an impact layer in the ' 'QGIS layers list or creating a new impact ' 'scenario before using the print tool.' '\nMessage: %s' % str(e)) raise Exception(myMessage) if self.layer.type() == QgsMapLayer.VectorLayer: return self.getVectorLegend() else: return self.getRasterLegend()
[docs] def getVectorLegend(self): """Get the legend for this layer as a graphic. Args: None Returns: A QImage object. self.legend is also populated with the image. Raises: An InvalidLegendLayer will be raised if a legend cannot be created from the layer. """ LOGGER.debug('InaSAFE Map getVectorLegend called') if not self.layer.isUsingRendererV2(): myMessage = self.tr('A legend can only be generated for ' 'vector layers that use the "new symbology" ' 'implementation in QGIS.') raise LegendLayerException(myMessage) # new symbology - subclass of QgsFeatureRendererV2 class self.legendImage = None myRenderer = self.layer.rendererV2() myType = myRenderer.type() if myType == "singleSymbol": mySymbol = myRenderer.symbol() self.addSymbolToLegend(theLabel=self.layer.name(), theSymbol=mySymbol) elif myType == "categorizedSymbol": for myCategory in myRenderer.categories(): mySymbol = myCategory.symbol() self.addSymbolToLegend( theCategory=myCategory.value().toString(), theLabel=myCategory.label(), theSymbol=mySymbol) elif myType == "graduatedSymbol": for myRange in myRenderer.ranges(): mySymbol = myRange.symbol() self.addSymbolToLegend(theMin=myRange.lowerValue(), theMax=myRange.upperValue(), theLabel=myRange.label(), theSymbol=mySymbol) else: #type unknown myMessage = self.tr('Unrecognised renderer type found for the ' 'impact layer. Please use one of these: ' 'single symbol, categorised symbol or ' 'graduated symbol and then try again.') raise LegendLayerException(myMessage) return self.legendImage
[docs] def getRasterLegend(self): """Get the legend for a raster layer as an image. Args: None Returns: An image representing the layer's legend. self.legend is also populated Raises: An InvalidLegendLayer will be raised if a legend cannot be created from the layer. """ LOGGER.debug('InaSAFE Map Legend getRasterLegend called') # test if QGIS 1.8.0 or older # see issue #259 if qgisVersion() <= 10800: myShader = self.layer.rasterShader().rasterShaderFunction() myRampItems = myShader.colorRampItemList() myLastValue = 0 # Making an assumption here... LOGGER.debug('Source: %s' % self.layer.source()) for myItem in myRampItems: myValue = myItem.value myLabel = myItem.label myColor = myItem.color print 'Value: %s Label %s' % (myValue, myLabel) self.addClassToLegend(myColor, theMin=myLastValue, theMax=myValue, theLabel=myLabel) myLastValue = myValue else: #TODO implement QGIS2.0 variant #In master branch, use QgsRasterRenderer::rasterRenderer() to # get/set how a raster is displayed. pass return self.legendImage
[docs] def addSymbolToLegend(self, theSymbol, theMin=None, theMax=None, theCategory=None, theLabel=None): """Add a class to the current legend. If the legend is not defined, a new one will be created. A legend is just an image file with nicely rendered classes in it. .. note:: This method just extracts the colour from the symbol and then delegates to the addClassToLegend function. Args: * theSymbol - **Required** symbol for the class as a QgsSymbol * theMin - Optional minimum value for the class * theMax - Optional maximum value for the class\ * theCategory - Optional category name (will be used in lieu of min/max) * theLabel - Optional text label for the class Returns: None Raises: Throws an exception if the class could not be added for some reason.. """ LOGGER.debug('InaSAFE Map Legend addSymbolToLegend called') myColour = theSymbol.color() self.addClassToLegend(myColour, theMin=theMin, theMax=theMax, theCategory=theCategory, theLabel=theLabel)
[docs] def addClassToLegend(self, theColour, theMin=None, theMax=None, theCategory=None, theLabel=None): """Add a class to the current legend. If the legend is not defined, a new one will be created. A legend is just an image file with nicely rendered classes in it. Args: * theColour - **Required** colour for the class as a QColor * theMin - Optional minimum value for the class * theMax - Optional maximum value for the class\ * theCategory - Optional category name (will be used in lieu of min/max) * theLabel - Optional text label for the class Returns: None Raises: Throws an exception if the class could not be added for some reason.. """ LOGGER.debug('InaSAFE Map Legend addClassToLegend called') self.extendLegend() myOffset = self.legendImage.height() - self.legendIncrement myPainter = QtGui.QPainter(self.legendImage) myBrush = QtGui.QBrush(theColour) myPainter.setBrush(myBrush) myPainter.setPen(theColour) myWhitespace = 0 # white space above and below each class itcon mySquareSize = self.legendIncrement - (myWhitespace * 2) myLeftIndent = 10 myPainter.drawRect(QtCore.QRectF(myLeftIndent, myOffset + myWhitespace, mySquareSize, mySquareSize)) myPainter.setPen(QtGui.QColor(0, 0, 0)) # outline colour myLabelX = myLeftIndent + mySquareSize + 10 myFontWeight = QtGui.QFont.Normal myItalicsFlag = False myFont = QtGui.QFont('verdana', self.legendFontSize, myFontWeight, myItalicsFlag) myPainter.setFont(myFont) myLabel = '' if theLabel: myLabel = theLabel if theMin is not None and theMax is not None: myLabel += ' [' + str(theMin) + ', ' + str(theMax) + ']' if theCategory is not None: myLabel = ' (' + theCategory + ')' myPainter.drawText(myLabelX, myOffset + 25, myLabel)
[docs] def extendLegend(self): """Grow the legend pixmap enough to accommodate one more legend entry. Args: None Returns: None Raises: Any exceptions raised by the InaSAFE library will be propogated. """ LOGGER.debug('InaSAFE Map Legend extendLegend called') if self.legendImage is None: self.legendImage = QtGui.QPixmap(self.legendWidth, 80) self.legendImage.fill(QtGui.QColor(255, 255, 255)) myPainter = QtGui.QPainter(self.legendImage) myFontSize = 16 myFontWeight = QtGui.QFont.Bold myItalicsFlag = False myFont = QtGui.QFont('verdana', myFontSize, myFontWeight, myItalicsFlag) myPainter.setFont(myFont) myPainter.drawText(10, 25, self.tr('Legend')) else: # extend the existing legend down for the next class myPixmap = QtGui.QPixmap(self.legendWidth, self.legendImage.height() + self.legendIncrement) myPixmap.fill(QtGui.QColor(255, 255, 255)) myPainter = QtGui.QPainter(myPixmap) myRect = QtCore.QRectF(0, 0, self.legendImage.width(), self.legendImage.height()) myPainter.drawPixmap(myRect, self.legendImage, myRect) self.legendImage = myPixmap