Skip to content

Commit

Permalink
added field pyculator algorithm
Browse files Browse the repository at this point in the history
fixed minor issues in algorithm execution and logging
added support for multiline strings
updated credits in about html page

git-svn-id: http://sextante.googlecode.com/svn/trunk/soft/bindings/qgis-plugin@334 881b9c09-3ef8-f3c2-ec3d-21d735c97f4d
  • Loading branch information
volayaf committed Aug 6, 2012
1 parent d4a316a commit 6bf0bfe
Show file tree
Hide file tree
Showing 8 changed files with 269 additions and 17 deletions.
4 changes: 3 additions & 1 deletion src/sextante/about/about.htm
Expand Up @@ -9,9 +9,11 @@ <h2>SEXTANTE for QGIS</h2>
<p>A development by Victor Olaya (volayaf@gmail.com).</p>
<p>Portions of this software contributed by:
<ul>
<li>Michael Nimm (mmqgis algorithms)</li>
<li>Alexander Bruy</li>
<li>Carson Farmer (fTools algorithms)</li>
<li>Julien Malik (Orfeo Toolbox connectors)</li>
<li>Evgeniy Nikulin (Original Field Pyculator code)</li>
<li>Michael Nimm (mmqgis algorithms)</li>
<li>Camilo Polymeris (Threading). Developed as part of Google Summer of Code 2012</li>
</ul>
</p>
Expand Down
217 changes: 217 additions & 0 deletions src/sextante/algs/FieldPyculator.py
@@ -0,0 +1,217 @@
from sextante.core.GeoAlgorithm import GeoAlgorithm
from sextante.outputs.OutputVector import OutputVector
from sextante.parameters.ParameterVector import ParameterVector
from qgis.core import *
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from sextante.parameters.ParameterString import ParameterString
from sextante.core.QGisLayers import QGisLayers
import os
from PyQt4 import QtGui
from sextante.core.GeoAlgorithmExecutionException import GeoAlgorithmExecutionException
from sextante.parameters.ParameterBoolean import ParameterBoolean
import sys


class FieldsPyculator(GeoAlgorithm):

INPUT_LAYER = "INPUT_LAYER"
USE_SELECTED = "USE_SELECTED"
FIELD_NAME = "FIELD_NAME"
#USE_GLOBAL = "USE_GLOBAL"
GLOBAL = "GLOBAL"
FORMULA = "FORMULA"
OUTPUT_LAYER ="OUTPUT_LAYER"
RESULT_VAR_NAME = "value"

def getIcon(self):
return QtGui.QIcon(os.path.dirname(__file__) + "/../images/toolbox.png")

def defineCharacteristics(self):
self.name = "Field Pyculator"
self.group = "Algorithms for vector layers"
self.addParameter(ParameterVector(self.INPUT_LAYER, "Input layer", ParameterVector.VECTOR_TYPE_ANY, False))
self.addParameter(ParameterBoolean(self.USE_SELECTED, "Use only selected features", False))
self.addParameter(ParameterString(self.FIELD_NAME, "Result field name", "NewField"))
#self.addParameter(ParameterBoolean(self.USE_GLOBAL, "Use global expression", False))
self.addParameter(ParameterString(self.GLOBAL, "Global expression", multiline = True))
self.addParameter(ParameterString(self.FORMULA, "Formula", "value = ", multiline = True))
self.addOutput(OutputVector(self.OUTPUT_LAYER, "Output layer"))


def processAlgorithm(self, progress):
fieldname = self.getParameterValue(self.FIELD_NAME)
code = self.getParameterValue(self.FORMULA)
globalExpression = self.getParameterValue(self.GLOBAL)
#useGlobal = self.getParameterValue(self.USE_GLOBAL)
useSelected = self.getParameterValue(self.USE_SELECTED)
settings = QSettings()
systemEncoding = settings.value( "/UI/encoding", "System" ).toString()
output = self.getOutputValue(self.OUTPUT_LAYER)
layer = QGisLayers.getObjectFromUri(self.getParameterValue(self.INPUT_LAYER))
vprovider = layer.dataProvider()
allAttrs = vprovider.attributeIndexes()
vprovider.select( allAttrs )
fields = vprovider.fields()
fields[len(fields)] = QgsField(fieldname, QVariant.Double)
writer = QgsVectorFileWriter( output, systemEncoding,fields, vprovider.geometryType(), vprovider.crs() )
outFeat = QgsFeature()
nFeatures = vprovider.featureCount()
nElement = 0
new_ns = {}

#run global code
if globalExpression.strip() != "":
try:
bytecode = compile(globalExpression, '<string>', 'exec')
exec bytecode in new_ns
except:
raise GeoAlgorithmExecutionException("FieldPyculator code execute error\n" +
"Global code block can't be executed!%s \n %s" %
(unicode(sys.exc_info()[0].__name__), unicode(sys.exc_info()[1])))

#replace all fields tags
field_map = vprovider.fields()
for num, field in field_map.iteritems():
field_name = unicode(field.name())
replval = '__attr[' + str(num) + ']'
code = code.replace("<"+field_name+">",replval)

#replace all special vars
code = code.replace('$id','__id')
code = code.replace('$geom','__geom')
need_id = code.find("__id") != -1
need_geom = code.find("__geom") != -1
need_attrs = code.find("__attr") != -1


#compile
try:
bytecode = compile(code, '<string>', 'exec')
except:
raise GeoAlgorithmExecutionException("FieldPyculator code execute error\n"+
"Field code block can't be executed! %s \n %s"
(unicode(sys.exc_info()[0].__name__), unicode(sys.exc_info()[1])))


#run
if not useSelected:
feat = QgsFeature()
if need_attrs:
attr_ind = vprovider.attributeIndexes()
else:
attr_ind = []
vprovider.select(attr_ind, QgsRectangle(), True)

while vprovider.nextFeature(feat):
progress.setPercentage(int((100 * nElement)/nFeatures))
attrMap = feat.attributeMap()
feat_id = feat.id()

#add needed vars
if need_id:
new_ns['__id'] = feat_id

if need_geom:
geom = feat.geometry()
new_ns['__geom'] = geom

if need_attrs:
attr = []
for num,a in attrMap.iteritems():
attr.append(self.Qvar2py(a))
new_ns['__attr'] = attr

#clear old result
if new_ns.has_key(self.RESULT_VAR_NAME):
del new_ns[self.RESULT_VAR_NAME]


#exec
#try:
exec bytecode in new_ns
#except:
# raise e
#===============================================================
# GeoAlgorithmExecutionException("FieldPyculator code execute error\n"+
# "Field code block can't be executed for feature %s\n%s\n%s" %
# (unicode(sys.exc_info()[0].__name__),
# unicode(sys.exc_info()[1]),
# unicode(feat_id)))
#===============================================================

#check result
if not new_ns.has_key(self.RESULT_VAR_NAME):
raise GeoAlgorithmExecutionException("FieldPyculator code execute error\n" +
"Field code block does not return '%s1' variable! Please declare this variable in your code!" %
self.RESULT_VAR_NAME)


#write feature
nElement += 1
outFeat.setGeometry( feat.geometry() )
outFeat.setAttributeMap( attrMap )
outFeat.addAttribute(len(vprovider.fields()), QVariant(new_ns[self.RESULT_VAR_NAME]))
writer.addFeature(outFeat)

else:
features = layer.selectedFeatures()
nFeatures = len(features)
for feat in features:
progress.setPercentage(int((100 * nElement)/nFeatures))
attrMap = feat.attributeMap()
feat_id = feat.id()

#add needed vars
if need_id:
new_ns['__id'] = feat_id

if need_geom:
geom = feat.geometry()
new_ns['__geom'] = geom

if need_attrs:
attrMap = feat.attributeMap()
attr = []
for num,a in attrMap.iteritems():
attr.append(self.Qvar2py(a))
new_ns['__attr'] = attr

#clear old result
if new_ns.has_key(self.RESULT_VAR_NAME):
del new_ns[self.RESULT_VAR_NAME]

#exec
exec bytecode in new_ns

#check result
if not new_ns.has_key(self.RESULT_VAR_NAME):
raise GeoAlgorithmExecutionException("FieldPyculator code execute error\n" +
"Field code block does not return '%s1' variable! Please declare this variable in your code!" %
self.RESULT_VAR_NAME)

#write feature
nElement += 1
outFeat.setGeometry( feat.geometry() )
outFeat.setAttributeMap( attrMap )
outFeat.addAttribute(len(vprovider.fields()), QVariant(new_ns[self.RESULT_VAR_NAME]))
writer.addFeature(outFeat)

del writer


def Qvar2py(self,qv):
if qv.type() == 2:
return qv.toInt()[0]
if qv.type() == 10:
return unicode(qv.toString())
if qv.type() == 6:
return qv.toDouble()[0]
return None


def checkParameterValuesBeforeExecuting(self):
##TODO check that formula is correct and fields exist
pass


3 changes: 2 additions & 1 deletion src/sextante/algs/SextanteAlgorithmProvider.py
Expand Up @@ -6,13 +6,14 @@
from sextante.algs.SaveSelectedFeatures import SaveSelectedFeatures
from sextante.algs.Explode import Explode
from sextante.algs.AutoincrementalField import AutoincrementalField
from sextante.algs.FieldPyculator import FieldsPyculator

class SextanteAlgorithmProvider(AlgorithmProvider):

def __init__(self):
AlgorithmProvider.__init__(self)
self.alglist = [AddTableField(), FieldsCalculator(), SaveSelectedFeatures(),
AutoincrementalField(), Explode()]
AutoincrementalField(), Explode(), FieldsPyculator()]

def initializeSettings(self):
AlgorithmProvider.initializeSettings(self)
Expand Down
11 changes: 4 additions & 7 deletions src/sextante/core/Sextante.py
Expand Up @@ -287,10 +287,6 @@ def runAlgorithm(algOrName, onFinish, *args):

msg = alg.checkParameterValuesBeforeExecuting()
if msg:
try:
QMessageBox.critical(None, "Unable to execute algorithm", msg)
return
except:
print ("Unable to execute algorithm\n" + msg)
return

Expand All @@ -304,7 +300,8 @@ def runAlgorithm(algOrName, onFinish, *args):
progress.setLabelText("Executing %s..." % alg.name)
def finish():
QApplication.restoreOverrideCursor()
onFinish(alg)
if onFinish is not None:
onFinish(alg)
progress.close()
def error(msg):
QApplication.restoreOverrideCursor()
Expand All @@ -323,8 +320,8 @@ def cancel():
algEx.start()
algEx.wait()
else:
UnthreadedAlgorithmExecutor.runalg(alg, SilentProgress())
if onFinish:
ret = UnthreadedAlgorithmExecutor.runalg(alg, SilentProgress())
if onFinish is not None and ret:
onFinish(alg)
QApplication.restoreOverrideCursor()
return alg
Expand Down
31 changes: 26 additions & 5 deletions src/sextante/gui/AlgorithmExecutionDialog.py
Expand Up @@ -18,7 +18,6 @@
from sextante.parameters.ParameterRange import ParameterRange
from sextante.parameters.ParameterNumber import ParameterNumber

from sextante.gui.ParametersPanel import ParametersPanel
from sextante.parameters.ParameterFile import ParameterFile
from sextante.parameters.ParameterCrs import ParameterCrs
from sextante.core.SextanteConfig import SextanteConfig
Expand All @@ -30,6 +29,7 @@
from sextante.core.WrongHelpFileException import WrongHelpFileException
import os
from sextante.gui.UnthreadedAlgorithmExecutor import UnthreadedAlgorithmExecutor
from sextante.parameters.ParameterString import ParameterString

try:
_fromUtf8 = QtCore.QString.fromUtf8
Expand Down Expand Up @@ -161,8 +161,14 @@ def setParamValue(self, param, widget):
return param.setValue(value)
elif isinstance(param, (ParameterNumber, ParameterFile, ParameterCrs, ParameterExtent)):
return param.setValue(widget.getValue())
elif isinstance(param, ParameterString):
if param.multiline:
return param.setValue(unicode(widget.toPlainText()))
else:
return param.setValue(unicode(widget.text()))

else:
return param.setValue(str(widget.text()))
return param.setValue(unicode(widget.text()))

@pyqtSlot()
def accept(self):
Expand Down Expand Up @@ -213,6 +219,18 @@ def accept(self):
SextanteLog.addToLog(SextanteLog.LOG_ALGORITHM, command)
if UnthreadedAlgorithmExecutor.runalg(self.alg, self):
self.finish()
else:
QApplication.restoreOverrideCursor()
keepOpen = SextanteConfig.getSetting(SextanteConfig.KEEP_DIALOG_OPEN)
if not keepOpen:
self.close()
else:
self.progressLabel.setText("")
self.progress.setMaximum(100)
self.progress.setValue(0)
self.buttonBox.button(QtGui.QDialogButtonBox.Ok).setEnabled(True)
self.buttonBox.button(QtGui.QDialogButtonBox.Close).setEnabled(True)
self.buttonBox.button(QtGui.QDialogButtonBox.Cancel).setEnabled(False)
else:
QMessageBox.critical(self, "Unable to execute algorithm", "Wrong or missing parameter values")

Expand All @@ -231,7 +249,7 @@ def finish(self):
self.progress.setValue(0)
self.buttonBox.button(QtGui.QDialogButtonBox.Ok).setEnabled(True)
self.buttonBox.button(QtGui.QDialogButtonBox.Close).setEnabled(True)
self.buttonBox.button(QtGui.QDialogButtonBox.Cancel).setEnabled(False)
self.buttonBox.button(QtGui.QDialogButtonBox.Cancel).setEnabled(False)

@pyqtSlot(str)
def error(self, msg):
Expand All @@ -246,6 +264,7 @@ def error(self, msg):
self.progressLabel.setText("")
self.progress.setValue(0)
self.buttonBox.button(QtGui.QDialogButtonBox.Ok).setEnabled(True)
self.buttonBox.button(QtGui.QDialogButtonBox.Close).setEnabled(True)

@pyqtSlot(int)
def iterate(self, i):
Expand All @@ -258,17 +277,19 @@ def cancel(self):
self.algEx.finished.disconnect()
self.algEx.terminate()
QApplication.restoreOverrideCursor()
self.buttonBox.button(QtGui.QDialogButtonBox.Ok).setEnabled(True)
self.buttonBox.button(QtGui.QDialogButtonBox.Close).setEnabled(True)
self.buttonBox.button(QtGui.QDialogButtonBox.Cancel).setEnabled(False)
except:
pass

@pyqtSlot(str)
def setInfo(self, msg, error = False):
if error:
SextanteLog.addToLog(SextanteLog.LOG_ERROR, msg)
#SextanteLog.addToLog(SextanteLog.LOG_ERROR, msg)
self.logText.append('<b>' + msg + '</b>')
else:
SextanteLog.addToLog(SextanteLog.LOG_INFO, msg)
#SextanteLog.addToLog(SextanteLog.LOG_INFO, msg)
self.logText.append(msg)

def setPercentage(self, i):
Expand Down
5 changes: 3 additions & 2 deletions src/sextante/gui/AlgorithmExecutor.py
Expand Up @@ -4,6 +4,7 @@
from sextante.core.GeoAlgorithmExecutionException import GeoAlgorithmExecutionException
from sextante.core.QGisLayers import QGisLayers
from sextante.core.SextanteUtils import SextanteUtils
import sys

class AlgorithmExecutor(QThread):
percentageChanged = pyqtSignal(int)
Expand Down Expand Up @@ -55,13 +56,13 @@ def runalg(self):
self.algorithm.execute(self.progress)
except GeoAlgorithmExecutionException,e :
self.error.emit(e.msg)
except BaseException,e:
except Exception,e:
self.error.emit(str(e))
print str(e)
# catch *all* errors, because QGIS tries to handle them in the GUI, which is fatal, this
# being a separate thread.
except:
print "Error executing " + str(self)
self.error.emit("Error executing " + str(self.alg.name) + "\n" + sys.exc_info()[0])

def runalgIterating(self):
try:
Expand Down

0 comments on commit 6bf0bfe

Please sign in to comment.