QGIS/python/plugins/processing/core/GeoAlgorithm.py

402 lines
16 KiB
Python
Raw Normal View History

2013-01-26 23:55:14 +01:00
# -*- coding: utf-8 -*-
"""
***************************************************************************
GeoAlgorithm.py
2013-01-26 23:55:14 +01:00
---------------------
Date : August 2012
Copyright : (C) 2012 by Victor Olaya
Email : volayaf at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************
"""
2013-01-26 23:55:14 +01:00
__author__ = 'Victor Olaya'
__date__ = 'August 2012'
__copyright__ = '(C) 2012, Victor Olaya'
2013-01-26 23:55:14 +01:00
# This will get replaced with a git SHA1 when you do a git archive
2013-01-26 23:55:14 +01:00
__revision__ = '$Format:%H$'
import os.path
import traceback
import subprocess
2013-01-26 23:55:14 +01:00
import copy
from qgis.PyQt.QtCore import QCoreApplication
2017-05-02 14:47:58 +10:00
from qgis.core import (QgsProcessingFeedback,
QgsSettings,
QgsProcessingAlgorithm,
QgsProject,
QgsProcessingUtils,
2017-05-16 16:36:00 +10:00
QgsProcessingParameterDefinition,
QgsMessageLog)
2016-09-04 17:58:17 +02:00
from builtins import str
from builtins import object
from processing.core.ProcessingConfig import ProcessingConfig
from processing.core.GeoAlgorithmExecutionException import GeoAlgorithmExecutionException
2017-03-03 20:44:03 +01:00
from processing.core.parameters import ParameterRaster, ParameterVector, ParameterMultipleInput, ParameterTable, Parameter
from processing.core.outputs import OutputVector, OutputRaster, OutputTable, OutputHTML, Output
from processing.algs.gdal.GdalUtils import GdalUtils
2017-04-25 20:03:39 +10:00
from processing.tools import dataobjects
2012-09-15 18:25:25 +03:00
2016-11-25 09:27:50 +02:00
class GeoAlgorithm(QgsProcessingAlgorithm):
2014-07-13 17:16:24 +02:00
2012-09-15 18:25:25 +03:00
def __init__(self):
super().__init__()
# Outputs generated by the algorithm
2012-09-15 18:25:25 +03:00
self.outputs = list()
# The crs taken from input layers (if possible), and used when
# loading output layers
2012-09-15 18:25:25 +03:00
self.crs = None
# If the algorithm is run as part of a model, the parent model
# can be set in this variable, to allow for customized
# behavior, in case some operations should be run differently
# when running as part of a model
self.model = None
2012-09-15 18:25:25 +03:00
# methods to overwrite when creating a custom geoalgorithm
def processAlgorithm(self, parameters, context, feedback):
"""Here goes the algorithm itself.
There is no return value from this method.
A GeoAlgorithmExecutionException should be raised in case
something goes wrong.
:param parameters:
:param context:
"""
2012-09-15 18:25:25 +03:00
pass
def getCustomModelerParametersDialog(self, modelAlg, algName=None):
"""If the algorithm has a custom parameters dialog when called
from the modeler, it should be returned here, ready to be
executed.
"""
2012-09-15 18:25:25 +03:00
return None
def processBeforeAddingToModeler(self, alg, model):
"""Add here any task that has to be performed before adding an algorithm
to a model, such as changing the value of a parameter depending on value
2016-11-25 09:27:50 +02:00
of another one"""
pass
# =========================================================
def execute(self, parameters, context=None, feedback=None, model=None):
"""The method to use to call a processing algorithm.
2012-09-15 18:25:25 +03:00
Although the body of the algorithm is in processAlgorithm(),
it should be called using this method, since it performs
some additional operations.
Raises a GeoAlgorithmExecutionException in case anything goes
wrong.
:param parameters:
"""
if feedback is None:
feedback = QgsProcessingFeedback()
if context is None:
2017-04-25 20:03:39 +10:00
context = dataobjects.createContext()
self.model = model
2012-09-15 18:25:25 +03:00
try:
self.setOutputCRS()
self.resolveOutputs()
self.runPreExecutionScript(feedback)
self.processAlgorithm(parameters, context, feedback)
feedback.setProgress(100)
self.convertUnsupportedFormats(context, feedback)
self.runPostExecutionScript(feedback)
2015-07-09 14:41:00 +02:00
except GeoAlgorithmExecutionException as gaee:
lines = [self.tr('Error while executing algorithm')]
lines.append(traceback.format_exc())
QgsMessageLog.logMessage(gaee.msg, self.tr('Processing'), QgsMessageLog.CRITICAL)
raise GeoAlgorithmExecutionException(gaee.msg, lines, gaee)
2015-07-09 14:41:00 +02:00
except Exception as e:
# If something goes wrong and is not caught in the
# algorithm, we catch it here and wrap it
lines = [self.tr('Uncaught error while executing algorithm')]
lines.append(traceback.format_exc())
QgsMessageLog.logMessage('\n'.join(lines), self.tr('Processing'), QgsMessageLog.CRITICAL)
2016-09-21 18:24:26 +02:00
raise GeoAlgorithmExecutionException(str(e) + self.tr('\nSee log for more details'), lines, e)
def runPostExecutionScript(self, feedback):
scriptFile = ProcessingConfig.getSetting(
ProcessingConfig.POST_EXECUTION_SCRIPT)
self.runHookScript(scriptFile, feedback)
def runPreExecutionScript(self, feedback):
scriptFile = ProcessingConfig.getSetting(
ProcessingConfig.PRE_EXECUTION_SCRIPT)
self.runHookScript(scriptFile, feedback)
def runHookScript(self, filename, feedback):
if filename is None or not os.path.exists(filename):
return
try:
script = 'import processing\n'
ns = {}
ns['feedback'] = feedback
ns['alg'] = self
with open(filename) as f:
lines = f.readlines()
for line in lines:
script += line
exec(script, ns)
except Exception as e:
QgsMessageLog.logMessage("Error in hook script: " + str(e), self.tr('Processing'), QgsMessageLog.WARNING)
# A wrong script should not cause problems, so we swallow
# all exceptions
pass
def convertUnsupportedFormats(self, context, feedback):
i = 0
feedback.setProgressText(self.tr('Converting outputs'))
for out in self.outputs:
if isinstance(out, OutputVector):
if out.compatible is not None:
layer = QgsProcessingUtils.mapLayerFromString(out.compatible, context)
if layer is None:
# For the case of memory layer, if the
# getCompatible method has been called
2013-02-03 10:26:43 +01:00
continue
writer = out.getVectorWriter(layer.fields(), layer.wkbType(), layer.crs(), context)
features = QgsProcessingUtils.getFeatures(layer, context)
for feature in features:
writer.addFeature(feature)
2013-02-07 01:09:39 +01:00
elif isinstance(out, OutputRaster):
if out.compatible is not None:
layer = QgsProcessingUtils.mapLayerFromString(out.compatible, context)
format = self.getFormatShortNameFromFilename(out.value)
orgFile = out.compatible
destFile = out.value
crsid = layer.crs().authid()
settings = QgsSettings()
2016-09-21 18:24:26 +02:00
path = str(settings.value('/GdalTools/gdalPath', ''))
envval = str(os.getenv('PATH'))
if not path.lower() in envval.lower().split(os.pathsep):
envval += '%s%s' % (os.pathsep, path)
os.putenv('PATH', envval)
command = 'gdal_translate -of %s -a_srs %s %s %s' % (format, crsid, orgFile, destFile)
if os.name == 'nt':
command = command.split(" ")
else:
command = [command]
proc = subprocess.Popen(
command,
shell=True,
stdout=subprocess.PIPE,
stdin=subprocess.PIPE,
stderr=subprocess.STDOUT,
universal_newlines=False,
)
proc.communicate()
2013-01-26 23:55:14 +01:00
elif isinstance(out, OutputTable):
if out.compatible is not None:
layer = QgsProcessingUtils.mapLayerFromString(out.compatible, context)
writer = out.getTableWriter(layer.fields())
features = QgsProcessingUtils.getFeatures(layer, context)
2013-01-26 23:55:14 +01:00
for feature in features:
2013-02-07 01:09:39 +01:00
writer.addRecord(feature)
feedback.setProgress(100 * i / float(len(self.outputs)))
def getFormatShortNameFromFilename(self, filename):
ext = filename[filename.rfind('.') + 1:]
supported = GdalUtils.getSupportedRasters()
2016-09-21 18:24:26 +02:00
for name in list(supported.keys()):
exts = supported[name]
if ext in exts:
return name
return 'GTiff'
def resolveOutputs(self):
"""Sets temporary outputs (output.value = None) with a
temporary file instead. Resolves expressions as well.
"""
try:
for out in self.outputs:
out.resolveValue(self)
2016-09-27 19:51:06 +02:00
except ValueError as e:
raise GeoAlgorithmExecutionException(str(e))
def setOutputCRS(self):
context = dataobjects.createContext()
layers = QgsProcessingUtils.compatibleLayers(QgsProject.instance())
2017-05-22 14:07:53 +10:00
for param in self.parameterDefinitions():
if isinstance(param, (ParameterRaster, ParameterVector, ParameterMultipleInput)):
if param.value:
if isinstance(param, ParameterMultipleInput):
inputlayers = param.value.split(';')
else:
inputlayers = [param.value]
for inputlayer in inputlayers:
for layer in layers:
if layer.source() == inputlayer:
self.crs = layer.crs()
return
p = QgsProcessingUtils.mapLayerFromString(inputlayer, context)
if p is not None:
self.crs = p.crs()
p = None
return
try:
from qgis.utils import iface
if iface is not None:
self.crs = iface.mapCanvas().mapSettings().destinationCrs()
except:
pass
def checkInputCRS(self, context=None):
"""It checks that all input layers use the same CRS. If so,
returns True. False otherwise.
"""
if context is None:
context = dataobjects.createContext()
crsList = []
2017-05-22 14:07:53 +10:00
for param in self.parameterDefinitions():
2016-01-08 12:52:19 +01:00
if isinstance(param, (ParameterRaster, ParameterVector, ParameterMultipleInput)):
if param.value:
if isinstance(param, ParameterMultipleInput):
layers = param.value.split(';')
else:
layers = [param.value]
for item in layers:
crs = QgsProcessingUtils.mapLayerFromString(item, context).crs()
if crs not in crsList:
crsList.append(crs)
return len(crsList) < 2
2012-09-15 18:25:25 +03:00
def addOutput(self, output):
# TODO: check that name does not exist
2012-09-15 18:25:25 +03:00
if isinstance(output, Output):
self.outputs.append(output)
def addParameter(self, param):
# TODO: check that name does not exist
2012-09-15 18:25:25 +03:00
if isinstance(param, Parameter):
self.parameters.append(param)
def setOutputValue(self, outputName, value):
for out in self.outputs:
if out.name == outputName:
out.setValue(value)
2012-09-15 18:25:25 +03:00
def getHTMLOutputsCount(self):
"""Returns the number of HTML outputs.
"""
i = 0
for out in self.outputs:
if isinstance(out, OutputHTML):
i += 1
return i
2012-09-15 18:25:25 +03:00
def removeOutputFromName(self, name):
for out in self.outputs:
if out.name == name:
self.outputs.remove(out)
def getOutputFromName(self, name):
for out in self.outputs:
if out.name == name:
return out
def getParameterValue(self, name):
for param in self.parameters:
if param.name == name:
return param.value
return None
def getOutputValue(self, name):
for out in self.outputs:
if out.name == name:
return out.value
return None
def getAsCommand(self):
"""Returns the command that would run this same algorithm from
the console.
Should return None if the algorithm cannot be run from the
console.
"""
s = 'processing.run("' + self.id() + '",'
2017-05-22 14:07:53 +10:00
for param in self.parameterDefinitions():
s += param.getValueAsCommandLineParameter() + ','
2012-09-15 18:25:25 +03:00
for out in self.outputs:
2017-05-16 16:36:00 +10:00
if not out.flags() & QgsProcessingParameterDefinition.FlagHidden:
s += out.getValueAsCommandLineParameter() + ','
s = s[:-1] + ')'
2012-09-15 18:25:25 +03:00
return s
2013-07-21 21:53:27 +02:00
def tr(self, string, context=''):
if context == '':
context = self.__class__.__name__
return QCoreApplication.translate(context, string)
def trAlgorithm(self, string, context=''):
if context == '':
context = self.__class__.__name__
return string, QCoreApplication.translate(context, string)
def executeAlgorithm(alg, parameters, context=None, feedback=None, model=None):
"""The method to use to call a processing algorithm.
Although the body of the algorithm is in processAlgorithm(),
it should be called using this method, since it performs
some additional operations.
Raises a GeoAlgorithmExecutionException in case anything goes
wrong.
:param parameters:
"""
if feedback is None:
feedback = QgsProcessingFeedback()
if context is None:
context = dataobjects.createContext()
#self.model = model
if True:
#self.setOutputCRS()
#self.resolveOutputs()
#self.evaluateParameterValues()
#self.runPreExecutionScript(feedback)
2017-05-15 19:01:15 +10:00
result = alg.run(parameters, context, feedback)
#self.processAlgorithm(parameters, context, feedback)
feedback.setProgress(100)
2017-05-15 19:01:15 +10:00
return result
#self.convertUnsupportedFormats(context, feedback)
#self.runPostExecutionScript(feedback)
#except GeoAlgorithmExecutionException as gaee:
#lines = [self.tr('Error while executing algorithm')]
# lines = []
# lines.append(traceback.format_exc())
#QgsMessageLog.logMessage(gaee.msg, self.tr('Processing'), QgsMessageLog.CRITICAL)
#raise GeoAlgorithmExecutionException(gaee.msg, lines, gaee)
#except Exception as e:
# If something goes wrong and is not caught in the
# algorithm, we catch it here and wrap it
#lines = [self.tr('Uncaught error while executing algorithm')]
# lines = []
# lines.append(traceback.format_exc())
#QgsMessageLog.logMessage('\n'.join(lines), self.tr('Processing'), QgsMessageLog.CRITICAL)
#raise GeoAlgorithmExecutionException(str(e) + self.tr('\nSee log for more details'), lines, e)