[GIS] Adding fields to shapefile from standalone PyQGIS script

pyqgisqgis-processing

When I run the following script via OSGEO4W64 Shell, I receive the following error when trying to run the qgis:fieldcalculator algorithm:

'NoneType' object has no attribute 'mapCanvas'

I read that the error message relates to the script trying to access an interface although I'm not sure. The algorithm does what it's supposed to with the output layer containing a new ID column, but the layer has no features saved.

Basically, I would like to add a new ID field using the formula $rownum and remove the longitude and latitude fields which are generated by the Create grid algorithm. I would also be happy if this can be achieved without having to use the Field calculator algorithm (ie. by directly accessing the data).

import os, sys, glob

from qgis.core import *
from PyQt4.QtGui import *
app = QApplication([])
QgsApplication.setPrefixPath("C:\\OSGeo4W64\\apps\\qgis", True)
QgsApplication.initQgis()

from os.path import expanduser
home = expanduser("~")

#   Folder path of the Results for shapefiles
path_dir = home + "\Desktop\Test\\"
path_res = path_dir + "Results\\"

# Prepare processing framework 
sys.path.append( home + '\.qgis2\python\plugins' )
from processing.core.Processing import Processing
Processing.initialize()
from processing.tools import *

def run():

    outputs_1=general.runalg("qgis:creategrid", 1000, 1000, 24108, 18351.157175, 258293.802316, 665638.226408, 1, 'EPSG:7405', None)
    outputs_2=general.runalg("qgis:fieldcalculator", outputs_1['SAVENAME'], 'ID', 1, 10, 0, True, '$rownum', None)
    outputs_3=processing.runalg("qgis:deletecolumn", outputs_2['OUTPUT_LAYER'], 'longitude', None)
    outputs_4=processing.runalg("qgis:deletecolumn", outputs_3['SAVENAME'], 'latitude', path_res  + "/" + "grid.shp")

#   Paths of the shapefiles in the Result folder with list comprehension
    output = [shp for shp in glob.glob(path_res + "*.shp")]

run()
QgsApplication.exitQgis()

Best Answer

The algorithm you are running requires some references to common QGIS components, which are available to plugins via an iface (QgisInterface) object. In this case you need a custom iface object because i) there is no QGIS instance running, ii) you are running a plugin, and iii) the algorithm you are running requires access to QGIS components (e.g. map canvas).

You can emulate the iface object by yourself, but thankfully my custom iface is included in Processing, so we can use it.

However, because of some changes to QgisInterface in the official QGIS project, you would need to either i) edit the file .../plugins/processing/tests/qgis_interface.py to make it equivalent to this one, or ii) download the file and replace the existing one. I've just changed 5 lines.

This is the working script:

import os, sys, glob

from qgis.core import *
from qgis.gui import QgsMapCanvas
from PyQt4.QtGui import *
app = QApplication([])
QgsApplication.setPrefixPath("/usr", True)
QgsApplication.initQgis()

from os.path import expanduser
home = expanduser("~")

#   Folder path of the Results for shapefiles
path_dir = home + "/Test/"
path_res = path_dir + "/Results"

# Prepare processing framework 
sys.path.append( home + '/.qgis2/python/plugins' )

# Get an iface object
canvas = QgsMapCanvas()
from processing.tests.qgis_interface import QgisInterface
iface = QgisInterface( canvas )

# Initialize the Processing plugin passing an iface object
from processing.ProcessingPlugin import ProcessingPlugin
plugin = ProcessingPlugin(iface)
from processing.tools import *

def run():

    outputs_1=general.runalg("qgis:creategrid", 1000, 1000, 24108, 18351.157175, 258293.802316, 665638.226408, 1, 'EPSG:7405', None)
    outputs_2=general.runalg("qgis:fieldcalculator", outputs_1['SAVENAME'], 'ID', 1, 10, 0, True, '$rownum', path_res  + "/" + "grid.shp")
    outputs_3=general.runalg("qgis:deletecolumn", outputs_2['OUTPUT_LAYER'], 'longitude', None)
    outputs_4=general.runalg("qgis:deletecolumn", outputs_3['SAVENAME'], 'latitude', path_res  + "/" + "grid.shp")

#   Paths of the shapefiles in the Result folder with list comprehension
    output = [shp for shp in glob.glob(path_res + "*.shp")]

run()
QgsApplication.exitQgis()
app.exit()

I tested it on GNU/Linux succesfully.