2

I'm writing a standalone script where I create a project, load layers, style them, call another scritp (.py) with functions to add a layer to the OpenStreetMap background and set the layout to export. At the end I would like to export it as pdf.

this is my main code.

import os
from qgis.core import *
from qgis.gui import *

import layout_functions as lf
###READ GEMEINDE'S FOLDER

#set the path to the gemeinde directory
wd = r"C:\Users\alvaro.garcia.daroca\OneDrive - Accenture\Documents\Qgis - Daroca\Telefónica - pruebas\NuevosGemeinden_2021-10-29\Abtweiler"
files = os.listdir(wd)    
#generate a list of the files to load
layers = list()
for file in files:
    if os.path.isfile(os.path.join(wd, file)) and (file.endswith('.gpkg') or file.endswith('.shp')):
        layers.append(file)
#reorder the layers according to orts, steiner, drops y buildings
slayers = [0,0,0,0]
nlayers = ['4. Orts', '3. Steiner', '2. Drops', '1. Buildings']
for layer in layers:
    if ('orts' in layer) == True: slayers[0] = layer
    elif ('teiner' in layer) == True: slayers[1] = layer
    elif ('rops' in layer) == True: slayers[2] = layer
    elif ('uildings' in layer) == True: slayers[3] = layer
print(slayers)

#we get the gemeinde code and the name, for this we get it from the file
#CodeGemeinde_NameGemeinde_orts800.gpkg
ortslayer = None
for layer in layers:
    if layer.endswith('orts800.gpkg'):
        ortslayer = layer
        break
    else:
        print('\nThere is not xxx_xxx_orts800.gpkg file')

gemeindeData = ortslayer.split('_')
gemeindeCode = gemeindeData[0]
gemeindeName = gemeindeData[1]
print('\nGemeinde:', gemeindeName)
print('Code:', gemeindeCode)

###GENERATE THE PROJECT BY LOADING THE LAYERS OF THE GEMEINDE

#supply path to qgis install location
QgsApplication.setPrefixPath("C:/PROGRA~1/QGIS32~1.0/apps/qgis", True)
#create a reference to the QgsApplication. setting the second argument to
#False disables the GUI
qgs = QgsApplication([], False)
#load providers
qgs.initQgis()
#get the project instance
project = QgsProject.instance()
#styles path of layers
BuildingsStyle = r'C:\Users\alvaro.garcia.daroca\OneDrive - Accenture\Documents\Qgis - Daroca\Layer Style\Buildings_Style.qml'
DropsStyle = r'C:\Users\alvaro.garcia.daroca\OneDrive - Accenture\Documents\Qgis - Daroca\Layer Style\Drops_Style.qml'
SteinersStyle = r'C:\Users\alvaro.garcia.daroca\OneDrive - Accenture\Documents\Qgis - Daroca\Layer Style\Steiners_Style.qml'
OrtsStyle = r'C:\Users\alvaro.garcia.daroca\OneDrive - Accenture\Documents\Qgis - Daroca\Layer Style\Orts_Style.qml'
paths = [OrtsStyle, SteinersStyle, DropsStyle, BuildingsStyle]
#add layers
for i in range(len(slayers)):
    vlayer = QgsVectorLayer(wd + "/" + slayers[i], nlayers[i], 'ogr') #QgsVectorLayer(path,name,'ogr')
    if not vlayer.isValid():
        print('Layer failed to load!')
    else:
        project.addMapLayer(vlayer, True)
        vlayer.loadNamedStyle(paths[i])

#call function layout from lf to create the layout
lf.layout('General', gemeindeName, gemeindeCode)
#call function export from lf to export the layout
lf.export('General', gemeindeName, wd)

project.write(wd + '/' + gemeindeName + '.qgs')

print('\nEND')
qgs.exitQgis()

I have two main problems, the first is that when I open the qgis project I am able to see the layers loaded in the layer manager, but the canvas is completely blank.

enter image description here

The second, I think it is derived from the first, is that the pdf exports fine but the map is blank.

enter image description here

This is the lf code, where I write the export function and the layout function. (I only publish the specified parts)

##Layout function
def layout(layoutName, gemeindeName, gemeindeCode):
    layout = QgsPrintLayout(project)
    #remove duplicated layouts
    layout_list = manager.printLayouts() #return a list of layouts
    for layout in layout_list:
        if layout.name() == layoutName:
            manager.removeLayout(layout)
    loadOSM()
    layout = QgsPrintLayout(project)
    layout.initializeDefaults() #initialize void layout
    #modify page format
    pc = layout.pageCollection()
    pc.pages()[0].setPageSize('A3', QgsLayoutItemPage.Orientation.Landscape)
    layout.setName(layoutName)
    manager.addLayout(layout)
    #add items to the layout
    addItemBox(layoutName, 'margen', 5, 5, 410, 287)
    addItemBox(layoutName, 'cajetín general', 8, 249, 404, 40)
    addItemBox(layoutName,'cajetín ubicación', 48, 252, 168, 34)
    addItemBox(layoutName,'cajetín fecha', 219, 252, 34, 34)
    addItemBox(layoutName,'cajetín descripción', 256, 252, 110, 34)
    addMap(layoutName, 'mapa principal', False, 8, 8, 297, 238)
    addItemPicture(layoutName, 'north arrow',r'C:\Users\alvaro.garcia.daroca\OneDrive - Accenture\Documents\Qgis - Daroca\Telefónica - pruebas\North_Arrow.svg', 15, 15, 20, 20)
    addItemPicture(layoutName, 'logo UGG', r'C:\Users\alvaro.garcia.daroca\OneDrive - Accenture\Documents\Qgis - Daroca\Telefónica - pruebas\UGG_logo.png', 369, 252, 40, 40)
    addItemPicture(layoutName, 'QR UGG',r'C:\Users\alvaro.garcia.daroca\OneDrive - Accenture\Documents\Qgis - Daroca\Telefónica - pruebas\QR_UGG.png', 11, 252, 34, 34)
    addLegend(layoutName)

##Function to add a map
def addMap(layoutName, id, atlas, x, y, w, h):
    layout = manager.layoutByName(layoutName) #selecciona el layout a trabajar
    map = QgsLayoutItemMap(layout)
    map.setRect(20, 20, 20, 20)
    canvas = QgsMapCanvas()
    map.setExtent(canvas.extent())
    map.setId(id) #set name id
    layout.addLayoutItem(map)
    map.setAtlasDriven(atlas) #set control by atlas (True or False)
    map.setFrameEnabled(True)
    map.setFrameStrokeWidth(QgsLayoutMeasurement(0.3))
    map.attemptMove(QgsLayoutPoint(x, y, QgsUnitTypes.LayoutMillimeters))
    map.attemptResize(QgsLayoutSize(w, h, QgsUnitTypes.LayoutMillimeters))

#Fucntion to export pdf
def export(layoutName, gemeindeName, path):
    basePath = path
    pdfPath = os.path.join(basePath, gemeindeName + '.pdf')
    layout = manager.layoutByName(layoutName)
    exporter = QgsLayoutExporter(layout)
    exporter.exportToPdf(pdfPath, QgsLayoutExporter.PdfExportSettings())

Does anyone know what I need to render the map and layers??

1
  • Can you check your layers contain something and share the screenshot. You could right-click on it and zoom to layer or open the attribute table. Nov 10 '21 at 14:01
2

Firstly, I think you should set a crs to your project. You can see in the bottom right of your screenshot that there is no crs associated with your project. You can do this simply like:

# 3857 (optionally) used since you have a wmts in your project
project.setCrs(QgsCoordinateReferenceSystem('epsg:3857'))

Secondly, you don't see any layers when you load your project in QGIS because there is no default extent set in the project's view settings. The syntax for setting it looks like this:

project.viewSettings().setDefaultViewExtent(QgsReferencedRectangle(<QgsRectangle>, <QgsCoordinateReferenceSystem>))

For the <QgsRectangle> and <QgsCoordinateReferenceSystem> arguments you can pass a layer extent and project or layer crs.

Or you could calculate the collective extent of all vector layers in your project and construct a corresponding QgsRectangle. You can see one way of doing that in my example below.

Finally, in the part of the code where you create your layout, you are instantiating an empty QgsMapCanvas object and setting your QgsLayoutItemMap object's extent to the empty canvas's extent. Again, you should instead set the layout map extent to a layer object extent or use a calculated rectangle of collective layer extents.

I created a minimal test script below which does something similar to yours. You can inspect parts of this script which should solve most of your problems.

import os
from qgis.core import *

QgsApplication.setPrefixPath("C:/OSGeo4W/apps/qgis", True)

qgs = QgsApplication([], False)

qgs.initQgis()

file_dir = 'C:\\Users\\Ben\\Desktop\\Project_folder'

layer_paths = [os.path.join(file_dir, f) for f in os.scandir(file_dir)
                if f.name.endswith('.gpkg')
                or f.name.endswith('shp')]
#print(layer_paths)

style_paths = [os.path.join(file_dir, f) for f in os.scandir(file_dir)
                if f.name.endswith('.qml')]
#print(style_paths)

layer_names = ['3.Protected_areas', '2.Water', '1.Rivers']
layers_to_add = []

for i, n in enumerate(layer_names):
    for p in layer_paths:
        if n[3:] in p:
            layers_to_add.insert(i, p)

project = QgsProject.instance()
proj_file_path = os.path.join(file_dir, 'Standalone_Project.qgs')
project.setFileName(proj_file_path)

project.setCrs(QgsCoordinateReferenceSystem('epsg:3857'))

osm_url = 'type=xyz&url=https://tile.openstreetmap.org/%7Bz%7D/%7Bx%7D/%7By%7D.png&zmax=19&zmin=0'
osm_lyr = QgsRasterLayer(osm_url, '4.OpenStreetMap', 'wms')
project.addMapLayer(osm_lyr)

# Store maximum and minimum values of layer bounding boxes
x_maxs = []
x_mins = []
y_maxs = []
y_mins = []

for index, path in enumerate(layers_to_add):
    lyr = QgsVectorLayer(path, layer_names[index], 'ogr')
    if lyr.isValid():
        # Transform layer extents to project crs
        transformed_extent = QgsCoordinateTransform(lyr.crs(), project.crs(), project).transform(lyr.extent())
        x_maxs.append(transformed_extent.xMaximum())
        x_mins.append(transformed_extent.xMinimum())
        y_maxs.append(transformed_extent.yMaximum())
        y_mins.append(transformed_extent.yMinimum())
        project.addMapLayer(lyr)
        lyr.loadNamedStyle([pth for pth in style_paths if layer_names[index][3:] in pth][0])

# Calculate collective extent of vector layers in project and...
# ...construct a QgsRectangle from x and y extremes
extent = QgsRectangle(min(x_mins), min(y_mins), max(x_maxs), max(y_maxs))
# Slightly increase extent outside of project vector layers
extent.grow(1000)#extent crs map units (project crs which is meters)
project.viewSettings().setDefaultViewExtent(QgsReferencedRectangle(extent, project.crs()))

# Create layout
layout = QgsPrintLayout(project)
layout.initializeDefaults()
layout.setName('Test Layout')
project.layoutManager().addLayout(layout)
map = QgsLayoutItemMap(layout)
map.attemptMove(QgsLayoutPoint(5,5, QgsUnitTypes.LayoutMillimeters))
map.attemptResize(QgsLayoutSize(200,150, QgsUnitTypes.LayoutMillimeters))
map.setFrameEnabled(True)
map.setFrameStrokeWidth(QgsLayoutMeasurement(0.3))
# Provide a meaningful extent to render
map.setExtent(extent)
layout.addLayoutItem(map)

legend = QgsLayoutItemLegend(layout)
legend.attemptMove(QgsLayoutPoint(210, 50, QgsUnitTypes.LayoutMillimeters))
legend.setFrameEnabled(True)
legend.setFrameStrokeWidth(QgsLayoutMeasurement(0.3))
legend.setFixedSize(QgsLayoutSize(50,40, QgsUnitTypes.LayoutMillimeters))
legend.refresh()
layout.addLayoutItem(legend)

project.write()

pdfPath = os.path.join(file_dir, 'Test Layout.pdf')
exporter = QgsLayoutExporter(layout)
exporter.exportToPdf(pdfPath, QgsLayoutExporter.PdfExportSettings())

qgs.exitQgis()

Default view when loading created project in QGIS:

enter image description here

Exported layout PDF:

enter image description here

3
  • Thank you very much, I am quite new to all this. Now I understood the reason for my blank map. If I may ask you a question: How should I select the correct crs? Is it correct if I load a project manually and copy the crs that Qgis sets by itself? Nov 14 '21 at 15:35
  • @Álvaro García Daroca, you're welcome. Projections and coordinate reference systems are a very broad topic. There are entire books and college courses on the subject. Simply for visualisation in QGIS, project crs is not critical, as all layers are reprojected on-the-fly, so crs selection may be more about appearance. However for data analysis and processing, using accurate, appropriate crs's may be very important (e.g. preserving angle, area etc). For example, I used Web Mercator (epsg:3857) as project crs in my example as OpenStreetMap was used (all wmts layers are in 3857 by default)...
    – Ben W
    Nov 17 '21 at 11:31
  • ...so I think it will give the best basemap quality in layouts. However, epsg:3857 should never be trusted for accurate distance or area calculations due to the extreme distortion, especially near the poles. It is very much "different horses for different courses" (if you know that expression). The best choice depends on region, scale and numerous other factors and requirements. Copying the crs of an existing project may not necessarily be the most reliable approach. It is probably worth doing some research and reading up on the matter, and making a reasoned decision about the best crs to use.
    – Ben W
    Nov 17 '21 at 11:39

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.