Saturday 20 May 2017

qgis 2.4 - Making serial maps from template using PyQGIS?


I have developed a stand-alone python script for making serial maps using PyQGIS library.


The maps should follow a specific format that is indicated by a template .qpt file.


This template file is opened and edited with lxml library in order to add information about the map (title, date, source, etc).


The modified .qpt is then loaded into a new composition, the layers are added and the map exported.


Everything was working perfectly since I’ve been using QGIS 2.2. Few days ago I have updated QGIS to the latest version 2.4 and now the output is sadly empty: map elements are displayed (frame, legend, title..), but no layer appears on the map.


I believe the script is correct, but maybe there’s something I should modify for running it with QGIS 2.4.


This is a very basic version of the script:


import os

from qgis.core import *
from qgis.gui import *
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4.QtXml import *
import lxml.etree as etree

print "setting prefix"
QgsApplication.setPrefixPath("/usr", True)
print "initiating qgis"

QgsApplication.initQgis()
print 'creating new app'
app = QgsApplication([], True)
#removing old layers
QgsMapLayerRegistry.instance().removeAllMapLayers()

script_folder = os.path.dirname(__file__)
project_folder = os.path.dirname(script_folder)
output_folder = os.path.join(project_folder, 'map_outputs')
xml_folder = os.path.join(project_folder, 'project_outputs')

shapefile_folder = os.path.join(project_folder, 'shapefile_folder')

template_composer = os.path.join(project_folder, 'basic_composer_template.qpt')
polyg_shapefile = os.path.join(shapefile_folder, 'polygon.shp') # crs EPSG:4326 - WGS 84
point_shapefile = os.path.join(shapefile_folder, 'point.shp') # crs EPSG:32615 - WGS 84 / UTM zone 15N

mapname = "Test Map"
srid = 4326
provider_name = 'ogr'
layerset = []


#add layer 1
vlayer_name= 'polygon layer'
vdata_source = polyg_shapefile
print "Loading EQ buffers"
layer = QgsVectorLayer(vdata_source, vlayer_name, provider_name)
print "Buffers loaded"
QgsMapLayerRegistry.instance().addMapLayer(layer)
layerset.append(layer.id())


#add layer 2
point_layer_name= 'point layer'
point_data_source = point_shapefile
point_layer = QgsVectorLayer(point_data_source, point_layer_name, provider_name)
QgsMapLayerRegistry.instance().addMapLayer(point_layer)
layerset.append(point_layer.id())

# Set up the map renderer that will be assigned to the composition
map_renderer = QgsMapRenderer()
#preparing the map the extent - 3 times wider than the polygon layer's extent

rect = layer.extent()
rect.scale(3)
# Set the labelling engine for the canvas
labelling_engine = QgsPalLabeling()
map_renderer.setLabelingEngine(labelling_engine)
# Enable on the fly CRS transformations
map_renderer.setProjectionsEnabled(True)
# Now set up the composition
composition = QgsComposition(map_renderer)
#set WGS84 as destination crs

map_projection = QgsCoordinateReferenceSystem(srid, QgsCoordinateReferenceSystem.PostgisCrsId)
map_projection_descr = map_projection.description()
map_renderer.setDestinationCrs(map_projection)

#open the composer template and edit it
with open(template_composer, 'r') as f:
tree = etree.parse(f)
#setting extent
for elem in tree.iter(tag = 'Extent'):
elem.attrib['xmax'] = str(rect.xMaximum())

elem.attrib['xmin'] = str(rect.xMinimum())
elem.attrib['ymax'] = str(rect.yMaximum())
elem.attrib['ymin'] = str(rect.yMinimum())
#editing the title
for elem in tree.iter(tag = 'ComposerLabel'):
for child in elem:
if child.tag == 'ComposerItem':
if child.attrib['id'] == "__maintitle__":
elem.attrib['labelText'] = mapname
#save the edited composer as a new file

new_composer = os.path.join(xml_folder, mapname + "_composer.qpt")
tree.write(new_composer)

#open the newly created composer
new_composerfile = file(new_composer, 'rt')
new_composer_content = new_composerfile.read()
new_composerfile.close()
document = QDomDocument()
document.setContent(new_composer_content)
result = composition.loadFromTemplate(document)


# Get the main map canvas on the composition and set the layers
composerMap = composition.getComposerMapById(0)
composerMap.renderModeUpdateCachedImage()
map_renderer.setLayerSet(layerset)

#legend
legend = QgsComposerLegend(composition)
legend.model().setLayerSet(map_renderer.layerSet())
legend.model().setLayerSet

composition.addItem(legend)
legend.setItemPosition (25,122)
legend.setFrameEnabled(True)
legend.setScale(.7)

#save image
print 'saving image'
image = composition.printPageAsRaster(0)
image.save(os.path.join(output_folder,mapname) + ".png")

Answer




The object QgsMapRenderer is deprecated in 2.4 and above, so the script wasn’t working properly.


To make the script work it’s necessary to replace it by the object QgsMapSettings. Consequently, also some function names need to be slightly changed (e.g. from setProjectionsEnabled() to setCrsTransformEnabled(), from setLayerSet() to setLayers())


This is the new corrected script, now working also with QGIS 2.4


import os
from qgis.core import *
from qgis.gui import *
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4.QtXml import *
import lxml.etree as etree


print "setting prefix"
QgsApplication.setPrefixPath("/usr", True)
print "initiating qgis"
QgsApplication.initQgis()
print 'creating new app'
app = QgsApplication([], True)
#removing old layers
QgsMapLayerRegistry.instance().removeAllMapLayers()


script_folder = os.path.dirname(__file__)
project_folder = os.path.dirname(script_folder)
output_folder = os.path.join(project_folder, 'map_outputs')
xml_folder = os.path.join(project_folder, 'project_outputs')
shapefile_folder = os.path.join(project_folder, 'shapefile_folder')

template_composer = os.path.join(project_folder, 'basic_composer_template.qpt')
polyg_shapefile = os.path.join(shapefile_folder, 'polygon.shp')
point_shapefile = os.path.join(shapefile_folder, 'point.shp')


mapname = "Test Map"
srid = 4326
provider_name = 'ogr'
layerset = []

#add layer 1
vlayer_name= 'polygon layer'
vdata_source = polyg_shapefile
print "Loading EQ buffers"
layer = QgsVectorLayer(vdata_source, vlayer_name, provider_name)

print "Buffers loaded"
QgsMapLayerRegistry.instance().addMapLayer(layer)
layerset.append(layer.id())

#add layer 2
point_layer_name= 'point layer'
point_data_source = point_shapefile
point_layer = QgsVectorLayer(point_data_source, point_layer_name, provider_name)
QgsMapLayerRegistry.instance().addMapLayer(point_layer)
layerset.append(point_layer.id())


# Set up the MapSetting object that will be assigned to the composition
ms = QgsMapSettings()
#preparing the map the extent - 3 times wider than the polygon layer's extent
rect = layer.extent()
rect.scale(3)

# Enable on the fly CRS transformations
ms.setCrsTransformEnabled(True)


composition = QgsComposition(ms)
#set WGS84 as destination crs
map_projection = QgsCoordinateReferenceSystem(srid, QgsCoordinateReferenceSystem.PostgisCrsId)
map_projection_descr = map_projection.description()
ms.setDestinationCrs(map_projection)

#open the composer template and edit it
with open(template_composer, 'r') as f:
tree = etree.parse(f)
#setting extent

for elem in tree.iter(tag = 'Extent'):
elem.attrib['xmax'] = str(rect.xMaximum())
elem.attrib['xmin'] = str(rect.xMinimum())
elem.attrib['ymax'] = str(rect.yMaximum())
elem.attrib['ymin'] = str(rect.yMinimum())
#editing the title
for elem in tree.iter(tag = 'ComposerLabel'):
for child in elem:
if child.tag == 'ComposerItem':
if child.attrib['id'] == "__maintitle__":

elem.attrib['labelText'] = mapname
#save the edited composer as a new file
new_composer = os.path.join(xml_folder, mapname + "_composer.qpt")
tree.write(new_composer)

#open the newly created composer
new_composerfile = file(new_composer, 'rt')
new_composer_content = new_composerfile.read()
new_composerfile.close()
document = QDomDocument()

document.setContent(new_composer_content)
result = composition.loadFromTemplate(document)

# Get the main map canvas on the composition and set the layers
composerMap = composition.getComposerMapById(0)
composerMap.renderModeUpdateCachedImage()
ms.setLayers(layerset)

#legend
legend = QgsComposerLegend(composition)

legend.model().setLayerSet(ms.layers())
legend.model().setLayerSet
composition.addItem(legend)
legend.setItemPosition (25,122)
legend.setFrameEnabled(True)
legend.setScale(.7)

#save image
print 'saving image'
image = composition.printPageAsRaster(0)

image.save(os.path.join(output_folder,mapname) + ".png")

No comments:

Post a Comment

arcpy - Changing output name when exporting data driven pages to JPG?

Is there a way to save the output JPG, changing the output file name to the page name, instead of page number? I mean changing the script fo...