0

I'm trying to make some map tiles (with a transparent background) showing the speed limit in different areas of the UK. I've successfully managed to do this using the QGIS GUI on Windows 10, but I'd now like to automate the process so I can easily keep the tiles up to date with changes to OpenStreetMap.

I've managed to get the process running in a docker container and it all runs through without any errors, but all of the generated tiles are blank (unlike when I run it with the QGIS GUI). The way the script works (after producing a bunch of .osm files for different speed limits), is to load the project (.qgs) that I had previously created in the QGIS GUI on Windows 10 and then run the qgis:tilesxyzdirectory process with the same parameters that were used in the GUI.

I suspect that the problem is in loading the project; I found a bit of code in the processing algorithm that creates a list of layers. The result of findLayers() is a list of QgsLayerTreeLayers, whereas the result of layerOrder() is a list of QgsVectorLayers, so the check that the entries in layerOrder appear in the visible list produces no results and hence self.layers gets set to []. Of course I might be completely wrong about the problem and, even if I'm right, I have no idea what to do about it. I'd like to be able to load a QGIS project as it allows me to use the GUI for styling.

The complete project containing the docker container details and all the generation scripts is on bitbucket here. After building with docker-compose build, I run it using docker-compose up. The actual python script for generating the tiles from the project is here.

The bash script that runs the overall process is here.

Can anyone see what I'm doing wrong?

  • This might be a dumb question, but are you sure your project file is a .qgs not a .qgz? – csk Jul 11 at 22:11
  • @csk I tried loading the .qgz but it failed (I can't remember the error message) so I unzipped the .qgz and extracted the .qgs that was inside. That loaded without any error messages. – DrAl Jul 12 at 5:33
  • I suspect the problem is that you are loading a static .qgs file which was created with different set of layers. As the layerIDs for new layers will be different than when the project was created, you get blank tiles. I suggest, you try creating a new project file with pyqgis and add your new layers to it before rendering it. See docs.qgis.org/testing/en/docs/pyqgis_developer_cookbook/… and docs.qgis.org/testing/en/docs/pyqgis_developer_cookbook/… – spatialthoughts Jul 12 at 6:40
0

As suggested by @spatialthoughts in his comment, I ended up having to load the layers manually in code. It seems a shame that the project can't be set up in the GUI and then run over and over again in the command line, but in the end it wasn't that hard to get the command line functionality working. The final code that generates the tiles is here in case anyone is interested:

import sys
import os

scriptdir = os.path.abspath(os.path.dirname(__file__))

print("Importing modules")
from qgis.core import (
        QgsApplication,
        QgsProject,
        QgsProcessingFeedback,
        QgsVectorLayer
        )
from qgis.PyQt.Qt import QColor

def run():
    print("Setting prefix path")
    QgsApplication.setPrefixPath('/usr', True) # Check this
    print("Creating application")
    qgs = QgsApplication([], False)
    print("Initialising application")
    qgs.initQgis()

    sys.path.append('/usr/share/qgis/python/plugins')

    print("Importing processing")
    import processing
    from processing.core.Processing import Processing
    print("Initialising processing")
    Processing.initialize()

    tiledir = sys.argv[1]

    ####

    layer_specifications = [
            {
                'file': '20_or_less.osm',
                'colour': [190,0,3,255],
                'alpha': 1.0,
                },
            {
                'file': 'low_speed_directional.osm',
                'colour': [255,127,0,255],
                'alpha': 1.0,
                },
            {
                'file': '30.osm',
                'colour': [164,113,88,255],
                'alpha': 1.0,
                },
            {
                'file': '40.osm',
                'colour': [255,158,23,255],
                'alpha': 1.0,
                },
            {
                'file': '50.osm',
                'colour': [231,113,72,255],
                'alpha': 1.0,
                },
            {
                'file': 'nsl.osm',
                'colour': [141,90,153,255],
                'alpha': 1.0,
                },
            ]
    for layer in layer_specifications:
        vlayer = QgsVectorLayer('./' + layer['file'] + '|layername=lines', 'name', 'ogr')
        if not vlayer.isValid():
            print("Layer failed to load: %s" % layer['file'])
            sys.exit(4)
        vlayer.renderer().symbol().setColor(QColor(*layer['colour']))
        vlayer.renderer().symbol().setOpacity(layer['alpha'])
        # One of these?:
        #QgsMapLayerRegistry.instance().addMapLayer(vlayer) # Import didn't work...
        QgsProject.instance().addMapLayer(vlayer)

    parameters = {
            'DPI' : 96,
            # Extent is GB limits
            # From the passed parameters, it looks like it is: west, east, south, north
            # This was the extent shown in the dialogue box in QGIS, but it causes
            # a maths domain error as the plugin expects degrees:
            #'EXTENT' : '-890009.4821566814,282147.8954041847,6361787.307678058,8643227.720708402 [EPSG:3857]',
            # Extracted from https://gist.githubusercontent.com/UsabilityEtc/6d2059bd4f0181a98d76/raw/12fa5f7b489c9babac255ab6bfe1c5cd7410ad64/uk-bounding-box.geojson
            # and manually bodged into shape...
            'EXTENT' : '-10.8544921875,2.021484375,49.82380908513249,59.478568831926395 [EPSG:3857]',
            # Small extent used for testing:
            #'EXTENT' : '-2.8544921875,-1.721484375,51.56380908513249,51.919568831926395 [EPSG:3857]',
            'OUTPUT_DIRECTORY' : tiledir + "/maxspeed",
            'OUTPUT_HTML' : './leaflet.html',
            'QUALITY' : 75,
            'TILE_FORMAT' : 0,
            'TILE_HEIGHT' : 256,
            'TILE_WIDTH' : 256,
            'TMS_CONVENTION' : False,
            'ZOOM_MAX' : 13,
            'ZOOM_MIN' : 9,
            }
    algorithm = "qgis:tilesxyzdirectory"

    print("Preparing feedback mechanism")
    feedback = QgsProcessingFeedback()
    print("Running process")
    res = processing.run(algorithm, parameters, feedback=feedback)
    print("Process complete: %r" % res)

    print("Exiting")
    sys.stdout.flush()
    qgs.exitQgis()
    print("Exiting function")
    sys.stdout.flush()
# If the process isn't run in a function, QGIS core dumps at the end for some reason
run()
print("All done")
sys.stdout.flush()

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.