Skip to content

Commit

Permalink
Create FieldMapper GeoAlgorithm
Browse files Browse the repository at this point in the history
  • Loading branch information
arnaud-morvan committed Feb 6, 2015
1 parent 88c928a commit 9e51488
Show file tree
Hide file tree
Showing 8 changed files with 1,056 additions and 0 deletions.
1 change: 1 addition & 0 deletions images/images.qrc
Expand Up @@ -478,6 +478,7 @@
<file>themes/default/cadtools/perpendicular.png</file>
<file>themes/default/mIconSuccess.png</file>
<file>themes/default/bubble.svg</file>
<file>themes/default/mIconClear.png</file>
</qresource>
<qresource prefix="/images/tips">
<file alias="symbol_levels.png">qgis_tips/symbol_levels.png</file>
Expand Down
137 changes: 137 additions & 0 deletions python/plugins/processing/algs/qgis/FieldsMapper.py
@@ -0,0 +1,137 @@
# -*- coding: utf-8 -*-

"""
***************************************************************************
FieldsMapper.py
---------------------
Date : October 2014
Copyright : (C) 2014 by Arnaud Morvan
Email : arnaud dot morvan at camptocamp 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. *
* *
***************************************************************************
"""

__author__ = 'Arnaud Morvan'
__date__ = 'October 2014'
__copyright__ = '(C) 2014, Arnaud Morvan'

# This will get replaced with a git SHA1 when you do a git archive

__revision__ = '$Format:%H$'


from qgis.core import QgsField, QgsExpression, QgsFeature
from processing.core.GeoAlgorithm import GeoAlgorithm
from processing.core.GeoAlgorithmExecutionException import \
GeoAlgorithmExecutionException
from processing.core.parameters import ParameterVector
from processing.core.outputs import OutputVector
from processing.tools import dataobjects, vector

from .fieldsmapping import ParameterFieldsMapping
from .ui.FieldsMapperDialogs import (FieldsMapperParametersDialog,
FieldsMapperModelerParametersDialog)

class FieldsMapper(GeoAlgorithm):

INPUT_LAYER = 'INPUT_LAYER'
FIELDS_MAPPING = 'FIELDS_MAPPING'
OUTPUT_LAYER = 'OUTPUT_LAYER'

def __init__(self):
GeoAlgorithm.__init__(self)
self.mapping = None

def defineCharacteristics(self):
self.name = 'Refactor fields'
self.group = 'Vector table tools'
self.addParameter(ParameterVector(self.INPUT_LAYER,
self.tr('Input layer'),
[ParameterVector.VECTOR_TYPE_ANY], False))
self.addParameter(ParameterFieldsMapping(self.FIELDS_MAPPING,
self.tr('Fields mapping'), self.INPUT_LAYER))
self.addOutput(OutputVector(self.OUTPUT_LAYER,
self.tr('Output layer')))

def getCustomParametersDialog(self):
return FieldsMapperParametersDialog(self)

def getCustomModelerParametersDialog(self, modelAlg, algIndex=None):
return FieldsMapperModelerParametersDialog(self, modelAlg, algIndex)

def processAlgorithm(self, progress):
layer = self.getParameterValue(self.INPUT_LAYER)
mapping = self.getParameterValue(self.FIELDS_MAPPING)
output = self.getOutputFromName(self.OUTPUT_LAYER)

layer = dataobjects.getObjectFromUri(layer)
provider = layer.dataProvider()
fields = []
expressions = []
for field_def in mapping:
fields.append(QgsField(name=field_def['name'],
type=field_def['type'],
len=field_def['length'],
prec=field_def['precision']))

expression = QgsExpression(field_def['expression'])
if expression.hasParserError():
raise GeoAlgorithmExecutionException(
self.tr(u'Parser error in expression "{}": {}')
.format(unicode(field_def['expression']),
unicode(expression.parserErrorString())))
expression.prepare(provider.fields())
if expression.hasEvalError():
raise GeoAlgorithmExecutionException(
self.tr(u'Evaluation error in expression "{}": {}')
.format(unicode(field_def['expression']),
unicode(expression.evalErrorString())))
expressions.append(expression)

writer = output.getVectorWriter(fields,
provider.geometryType(),
layer.crs())

# Create output vector layer with new attributes
error = ''
calculationSuccess = True
inFeat = QgsFeature()
outFeat = QgsFeature()
features = vector.features(layer)
count = len(features)
for current, inFeat in enumerate(features):
rownum = current + 1

outFeat.setGeometry(inFeat.geometry())

attrs = []
for i in xrange(0, len(mapping)):
field_def = mapping[i]
expression = expressions[i]
expression.setCurrentRowNumber(rownum)
value = expression.evaluate(inFeat)
if expression.hasEvalError():
calculationSuccess = False
error = expression.evalErrorString()
break

attrs.append(value)
outFeat.setAttributes(attrs)

writer.addFeature(outFeat)

current += 1
progress.setPercentage(100 * current / float(count))

del writer

if not calculationSuccess:
raise GeoAlgorithmExecutionException(
self.tr('An error occurred while evaluating the calculation'
' string:\n') + error)
2 changes: 2 additions & 0 deletions python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py
Expand Up @@ -125,6 +125,7 @@
from SelectByExpression import SelectByExpression
from HypsometricCurves import HypsometricCurves
from SplitLinesWithLines import SplitLinesWithLines
from processing.algs.qgis.FieldsMapper import FieldsMapper

import processing.resources_rc

Expand Down Expand Up @@ -171,6 +172,7 @@ def __init__(self):
SetVectorStyle(), SetRasterStyle(),
SelectByExpression(), HypsometricCurves(),
SplitLinesWithLines(), CreateConstantRaster(),
FieldsMapper(),
]

if hasMatplotlib:
Expand Down
55 changes: 55 additions & 0 deletions python/plugins/processing/algs/qgis/fieldsmapping.py
@@ -0,0 +1,55 @@
# -*- coding: utf-8 -*-

"""
***************************************************************************
FieldsMapper.py
---------------------
Date : October 2014
Copyright : (C) 2014 by Arnaud Morvan
Email : arnaud dot morvan at camptocamp 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. *
* *
***************************************************************************
"""

__author__ = 'Arnaud Morvan'
__date__ = 'October 2014'
__copyright__ = '(C) 2014, Arnaud Morvan'

# This will get replaced with a git SHA1 when you do a git archive

__revision__ = '$Format:%H$'


from processing.core.parameters import Parameter


class ParameterFieldsMapping(Parameter):

def __init__(self, name='', description='', parent=None):
Parameter.__init__(self, name, description)
self.parent = parent
self.value = []

def getValueAsCommandLineParameter(self):
return '"' + unicode(self.value) + '"'

def setValue(self, value):
if value is None:
return False
if isinstance(value, list):
self.value = value
return True
if isinstance(value, unicode):
try:
self.value = eval(value)
return True
except Exception as e:
print unicode(e) # display error in console
return False
return False
140 changes: 140 additions & 0 deletions python/plugins/processing/algs/qgis/ui/FieldsMapperDialogs.py
@@ -0,0 +1,140 @@
# -*- coding: utf-8 -*-

"""
***************************************************************************
FieldsMapper.py
---------------------
Date : October 2014
Copyright : (C) 2014 by Arnaud Morvan
Email : arnaud dot morvan at camptocamp 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. *
* *
***************************************************************************
"""

__author__ = 'Arnaud Morvan'
__date__ = 'October 2014'
__copyright__ = '(C) 2014, Arnaud Morvan'

# This will get replaced with a git SHA1 when you do a git archive

__revision__ = '$Format:%H$'


from PyQt4.QtGui import QComboBox, QSpacerItem

from processing.core.parameters import ParameterVector
from processing.tools import dataobjects
from processing.gui.ParametersPanel import ParametersPanel
from processing.gui.AlgorithmDialog import AlgorithmDialog, AlgorithmDialogBase
from processing.modeler.ModelerParametersDialog import ModelerParametersDialog

from processing.algs.qgis.fieldsmapping import ParameterFieldsMapping
from .FieldsMappingPanel import FieldsMappingPanel


class FieldsMapperParametersPanel(ParametersPanel):

def __init__(self, parent, alg):
ParametersPanel.__init__(self, parent, alg)

item = self.layoutMain.itemAt(self.layoutMain.count() - 1)
if isinstance(item, QSpacerItem):
self.layoutMain.removeItem(item)
item = None

def getWidgetFromParameter(self, param):
if isinstance(param, ParameterFieldsMapping):
item = FieldsMappingPanel()
if param.parent in self.dependentItems:
items = self.dependentItems[param.parent]
else:
items = []
self.dependentItems[param.parent] = items
items.append(param.name)
parent = self.alg.getParameterFromName(param.parent)
if isinstance(parent, ParameterVector):
layers = dataobjects.getVectorLayers(parent.shapetype)
else:
layers = dataobjects.getTables()
if len(layers) > 0:
item.setLayer(layers[0])
return item
return ParametersPanel.getWidgetFromParameter(self, param)

def updateDependentFields(self):
sender = self.sender()
if not isinstance(sender, QComboBox):
return
if not sender.name in self.dependentItems:
return
layer = sender.itemData(sender.currentIndex())
children = self.dependentItems[sender.name]
for child in children:
widget = self.valueItems[child]
if isinstance(widget, FieldsMappingPanel):
widget.setLayer(layer)

def somethingDependsOnThisParameter(self, parent):
for param in self.alg.parameters:
if isinstance(param, ParameterFieldsMapping):
if param.parent == parent.name:
return True
return False


class FieldsMapperParametersDialog(AlgorithmDialog):
def __init__(self, alg):
AlgorithmDialogBase.__init__(self, alg)

self.alg = alg

self.mainWidget = FieldsMapperParametersPanel(self, alg)
self.setMainWidget()

def setParamValue(self, param, widget, alg=None):
if isinstance(param, ParameterFieldsMapping):
return param.setValue(widget.value())
return AlgorithmDialog.setParamValue(self, param, widget, alg)


class FieldsMapperModelerParametersDialog(ModelerParametersDialog):

def __init__(self, alg, model, algName=None):
ModelerParametersDialog.__init__(self, alg, model, algName)

paramsLayout = self.paramPanel.layout()
item = paramsLayout.itemAt(paramsLayout.count() - 1)
if isinstance(item, QSpacerItem):
paramsLayout.removeItem(item)
item = None

def getWidgetFromParameter(self, param):
if isinstance(param, ParameterFieldsMapping):
return FieldsMappingPanel()
return ModelerParametersDialog.getWidgetFromParameter(self, param)

def setPreviousValues(self):
ModelerParametersDialog.setPreviousValues(self)
if self._algName is not None:
alg = self.model.algs[self._algName]
for param in alg.algorithm.parameters:
if isinstance(param, ParameterFieldsMapping):
widget = self.valueItems[param.name]
value = alg.params[param.name]
if isinstance(value, unicode):
# convert to list because of ModelerAlgorithme.resolveValue behavior with lists
value = eval(value)
widget.setValue(value)

def setParamValue(self, alg, param, widget):
if isinstance(param, ParameterFieldsMapping):
# convert to unicode because of ModelerAlgorithme.resolveValue behavior with lists
alg.params[param.name] = unicode(widget.value())
return True
return ModelerParametersDialog.setParamValue(self, alg, param, widget)

0 comments on commit 9e51488

Please sign in to comment.