[GIS] Print Composer Map with Table using PyQGIS

print-composerpyqgisqgis

I am looking for a way to print a custom feature form created in PyQt Designer for QGIS 2.4 using PyQGIS. My goal is to print/save image as the current extent of the map with the selected feature's attributes of interest on the same page.

I'm experimenting with two different ways:

First, is to use QgsComposition and QPrinter to add and print items to a pdf: including the map, legend, and attributes. My issue here is getting the only the desired attributes to display dynamically changing depending on the active layer. If I could add my feature form as a map item, I would be displaying the attributes added to the custom form.

UPDATE: The following code may be able to display my map with a legend and selective table, but I still have many issues with the map items (no text in the table, positioning, unable to include only desired layer in legend). I've included all the code associated with this print composer:

layer = qgis.utils.iface.activeLayer()

qgis.utils.iface.actionZoomToSelected().trigger()
qgis.utils.iface.mapCanvas().zoomScale(1000)

mapRenderer = iface.mapCanvas().mapRenderer()
c = QgsComposition(mapRenderer)
c.setPlotStyle(QgsComposition.Print)
x, y = 0, 0
w, h = c.paperWidth(), c.paperHeight()
composerMap = QgsComposerMap(c, x,y,w*.75,h)
c.addItem(composerMap)

legend = QgsComposerLegend(c)
c.addComposerLegend(legend)
legend.model().setLayerSet(mapRenderer.layerSet())
legend.setTitle('')

legendModel = QgsLegendModel(c)
legendModel.setLayerSet([layer.id()])

legend.updateLegend()

selected_ids = [f.id() for f in layer.selectedFeatures()]
filter_id_string = ','.join([str(id) for id in selected_ids])

table = QgsComposerAttributeTable(c)
table.setVectorLayer(layer)
table.setMaximumNumberOfFeatures(20)
table.setFeatureFilter("id in (" + filter_id_string + ")")
table.setFilterFeatures(True)
table.setItemPosition(x+50, y+50)
table.applyDefaultSize()
col1 = QgsComposerTableColumn()
col1.setAttribute('Diametre')
col2 = QgsComposerTableColumn()
col2.setAttribute('Nature')
table.setColumns([col1, col2])
c.addItem(table)

printer = QPrinter()
printer.setOutputFormat(QPrinter.PdfFormat)
printer.setOutputFileName('out.pdf')
printer.setPaperSize(QSizeF(c.paperWidth(), c.paperHeight()), QPrinter.Millimeter)
printer.setFullPage(True)
printer.setColorMode(QPrinter.Color)
printer.setResolution(c.printResolution())
pdfPainter = QPainter(printer)
paperRectMM = printer.pageRect(QPrinter.Millimeter)
paperRectPixel = printer.pageRect(QPrinter.DevicePixel)
c.render(pdfPainter, paperRectPixel, paperRectMM)
pdfPainter.end()

Second, is to embed a image of the current extent (using mapCanvas().saveasImage()) using a dynamic QPixMap element on the custom form. This brings me back to the same question as to whether or not the custom feature form could be printed (to pdf, and eventually paper).

Best Answer

Which version of QGIS are you using? If it's 2.4, you can set the columns shown in a QgsComposerAttributeTable by using the setColumns method. This method takes a list of QgsComposerTableColumn. Here's an example

#create a composition and composer attribute table
composition = QgsComposition(iface.mapCanvas().mapSettings())
table = QgsComposerAttributeTable(composition)

#create columns for the table
col1 = QgsComposerTableColumn()
col1.setAttribute('feature_id')
col2 = QgsComposerTableColumn()
col2.setAttribute('feature_name')

#set table columns
table.setColumns([col1, col2])

There's a bunch of other things you can set for columns, such as the heading, alignment and sort order (see api docs here). You can even set an expression for a column in place of the attribute for added flexibility.

edit: To show only certain features you can set a filter on the table:

table.setFeatureFilter("id in (1,2,3)")
table.setFilterFeatures( True )

So, to filter a table to only selected features:

#get current layer
current_layer = qgis.utils.iface.mapCanvas().currentLayer()
#get list of selected feature ids
selected_ids = [f.id() for f in current_layer.selectedFeatures()]
#convert list to string
filter_id_string = ','.join([str(id) for id in selected_ids])
#filter table
table.setFeatureFilter("$id in (" + filter_id_string + ")")
table.setFilterFeatures( True )

update: I missed a character in the filter expression. It should have been "$id in (...". Here's a complete, working version of the code you posted. There was a few simple mistakes (the constructor for QgsLegendModel takes no arguments, there's no applyDefaultSize method for attribute tables, and I've also moved the attribute table setItemPosition line):

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

layer = qgis.utils.iface.activeLayer()

qgis.utils.iface.actionZoomToSelected().trigger()
qgis.utils.iface.mapCanvas().zoomScale(1000)

mapRenderer = iface.mapCanvas().mapRenderer()
c = QgsComposition(mapRenderer)
c.setPlotStyle(QgsComposition.Print)
x, y = 0, 0
w, h = c.paperWidth(), c.paperHeight()
composerMap = QgsComposerMap(c, x,y,w*.75,h)
c.addItem(composerMap)

legend = QgsComposerLegend(c)
c.addComposerLegend(legend)
legend.model().setLayerSet(mapRenderer.layerSet())
legend.setTitle('')

legendModel = QgsLegendModel()
legendModel.setLayerSet([layer.id()])

legend.updateLegend()

selected_ids = [f.id() for f in layer.selectedFeatures()]
filter_id_string = ','.join([str(id) for id in selected_ids])

table = QgsComposerAttributeTable(c)
table.setItemPosition(x+50, y+50)
table.setVectorLayer(layer)
table.setMaximumNumberOfFeatures(20)
table.setFeatureFilter("$id in (" + filter_id_string + ")")
table.setFilterFeatures(True)
col1 = QgsComposerTableColumn()
col1.setAttribute('Diametre')
col1.setHeading("Diametre")
col2 = QgsComposerTableColumn()
col2.setAttribute('Nature')
col2.setHeading("Nature")
table.setColumns([col1, col2])
c.addItem(table)

printer = QPrinter()
printer.setOutputFormat(QPrinter.PdfFormat)
printer.setOutputFileName('out.pdf')
printer.setPaperSize(QSizeF(c.paperWidth(), c.paperHeight()), QPrinter.Millimeter)
printer.setFullPage(True)
printer.setColorMode(QPrinter.Color)
printer.setResolution(c.printResolution())
pdfPainter = QPainter(printer)
paperRectMM = printer.pageRect(QPrinter.Millimeter)
paperRectPixel = printer.pageRect(QPrinter.DevicePixel)
c.render(pdfPainter, paperRectPixel, paperRectMM)
pdfPainter.end()
Related Question