I created some PyQGIS functions to set up a print composer, modify the layout (adding maps, legend, title...) and print the result as an image file. This works fine with one or several vector layers - all of them are shown in the resulting image file.
However, when I try to create a print composer layout that includes a layer from the Openlayers plugin, I do not get the expected result - most of the time the basemap layer is missing at all and sometimes some artifacts show up.
The example code below should be instantly executable after setting the plotdir
parameter at the top to a desired output folder. It
- creates a new QGIS project
- creates two vector layers in memory, each with one point geometry somewhere
- loads the OpenStreetMap basemap from the OpenLayers plugin (by clicking through the QGIS Gui menu as I don't know how to load it otherwise)
- passes the two memory layers and the Openlayers layer to a newly created print composer, sets some layout stuff (legend, title...) and saves the result to an image file.
When I run this, the legend shows all three desired layers, but the canvas is blank (I would expect to see at least the basemap as the point geometries are rather small). Any suggestions how to properly include a basemap into a print composer with PyQGIS? I tried this on Windows 7 with QGIS 2.4 and 2.6.
The code:
# imports
from qgis.core import *
from qgis.utils import *
from PyQt4.QtCore import *
from PyQt4.QtGui import *
plotdir = r'F:\\'
###################################
# function to start a new qgis project
###################################
def startNewProject():
print('starting new project')
iface.newProject(thePromptToSaveFlag = False)
return
###################################
# function to create a memory layer and add it to the map layer registry
###################################
def createMemoryLayer(layerType, layerName, x, y):
print 'create memory layer'
myMemoryLayer = QgsVectorLayer(layerType, layerName, "memory")
QgsMapLayerRegistry.instance().addMapLayer(myMemoryLayer, True)
feat = QgsFeature()
feat.setGeometry(QgsGeometry.fromPoint(QgsPoint(x,y)))
(res, outFeats) = myMemoryLayer.dataProvider().addFeatures( [ feat ] )
return myMemoryLayer
###################################
# function to load OpenLayers plugin layers and move it to the bottom of the layer tree
###################################
def loadOpenLayersPluginMap(mapProvider, openLayersMap):
print 'loading openlayers plugin layer'
webMenu = iface.webMenu() #get object of the Web menu of QGIS
for webMenuItem in webMenu.actions(): #open the Web menu of QGIS and loop through the list of web plugins
if 'OpenLayers plugin' in webMenuItem.text(): #look for OpenLayers plugin entry in the Web menu
openLayersMenu = webMenuItem #and open it
for openLayersMenuItem in openLayersMenu.menu().actions(): #open the OpenLayers plugin menu entry and loop through the list of map providers
if mapProvider in openLayersMenuItem.text(): #if the desired mapProvider is found
mapProviderMenu = openLayersMenuItem #open its menu entry
for map in mapProviderMenu.menu().actions(): #loop through the list of maps for the opened provider
if openLayersMap in map.text(): #if the desired map entry is found
map.trigger() #click the entry to load the map as a layer
# move the layer to the bottom of the TOC by putting all other layers above it (openlayers layer cannot be moved directly)
root = QgsProject.instance().layerTreeRoot()
for child in root.children():
if isinstance(child, QgsLayerTreeLayer):
childClone = child.clone()
root.insertChildNode(0, childClone)
root.removeChildNode(child)
return
###################################
# function to zoom to the extent of a layer
###################################
def zoomToLayerExtent(layer):
print 'zooming to layer '
# set the desired layer as active and zoom to it
iface.setActiveLayer(layer)
iface.zoomToActiveLayer()
return
###################################
# function to save a map layout as image file with map composer
###################################
def saveImagesWithPrintComposer(layers, extentLayer, plottitle, plotdir):
print 'saving images with map composer'
#######
# set up layer set, extent and create a map renderer
#######
mapRenderer = QgsMapSettings() # new in QGIS 2.4 - replaces QgsMapRenderer()
layerSet = [layer.id() for layer in layers] # create a list of all layer IDs that shall be part of the composition
mapRenderer.setLayers(layerSet) # when using QgsMapRenderer, replace this with setLayerSet(layerset)
mapRectangle = extentLayer.extent() # set extent to the extent of the desired layer
mapRectangle.scale(2) # set scale as desired
mapRenderer.setExtent(mapRectangle)
mapRenderer.setOutputSize(QSize(1600,1200)) #when using QgsMapRenderer(), setOutputSize needs a second argument '[int] dpi'
#######
# create a composition and pass the renderer
#######
composition = QgsComposition(mapRenderer)
composition.setPlotStyle(QgsComposition.Print)
dpi = composition.printResolution()
dpmm = dpi / 25.4
width = int(dpmm * composition.paperWidth())
height = int(dpmm * composition.paperHeight())
#######
# add map to the composition
#######
x, y = 0, 0
w, h = composition.paperWidth(), composition.paperHeight()
composerMap = QgsComposerMap(composition, x, y, w, h)
composition.addItem(composerMap)
#######
# create title label
#######
composerLabel = QgsComposerLabel(composition)
composerLabel.setText(plottitle)
composerLabel.setFont(QFont("Arial",8))
composerLabel.adjustSizeToText()
composerLabel.setBackgroundEnabled(False)
composition.addItem(composerLabel)
composerLabel.setItemPosition(20,10)
#######
# create legend
#######
# create composer legend object
composerLegend = QgsComposerLegend(composition)
# set legend layers set (as the same as the map layerset)
composerLegend.model().setLayerSet(mapRenderer.layers()) #when using QgsMapRenderer, use layerSet() instead of layers()
# set titles
composerLegend.setTitle('')
newFont = QFont("Arial", 8)
composerLegend.setStyleFont(QgsComposerLegendStyle.Title, newFont)
composerLegend.setStyleFont(QgsComposerLegendStyle.Subgroup, newFont)
composerLegend.setStyleFont(QgsComposerLegendStyle.SymbolLabel, newFont)
# refresh legend
composerLegend.updateLegend()
# set feature count activated for all layers of the current map composition
composerLayerItem = QgsComposerLayerItem()
def activateFeatureCount(layer):
composerLayerItem.setLayerID(layer.id())
composerLayerItem.setShowFeatureCount(True)
return
[activateFeatureCount(layer) for layer in layers]
# set legend background
composerLegend.setBackgroundEnabled(False)
# set legend position
composerLegend.setItemPosition(20,20)
# add legend to composition
composition.addItem(composerLegend)
#######
# create image and initialize
#######
image = QImage(QSize(width, height), QImage.Format_ARGB32)
image.setDotsPerMeterX(dpmm * 1000)
image.setDotsPerMeterY(dpmm * 1000)
image.fill(0)
#######
# Render composition
#######
painter = QPainter(image)
sourceArea = QRectF(0, 0, composition.paperWidth(), composition.paperHeight())
targetArea = QRectF(0, 0, width, height)
composition.render(painter, targetArea, sourceArea)
painter.end()
#######
# Save composition to image file (other extensions possible)
#######
image.save(plotdir + plottitle + '.png', 'png')
return
###################################
# main function
###################################
def main():
# start a new qgis project
startNewProject()
# create memory layers
memoryLayer1 = createMemoryLayer('Linestring', 'MyFirstLayer', 2, 47) # some random coordinates
memoryLayer2 = createMemoryLayer('Linestring', 'MySecondLayer', 9, 48) # some random coordinates
# add openlayers plugin layer
loadOpenLayersPluginMap('OpenStreetMap', 'OpenStreetMap')
# zoom to first layer
zoomToLayerExtent(memoryLayer1)
# fetch all layers from the canvas
allLayers = QgsMapLayerRegistry.instance().mapLayers().values()
# save to file with print composer
saveImagesWithPrintComposer(allLayers, memoryLayer1, 'MyPlot', plotdir)
return
main()
print 'finished'