mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-19 00:04:52 -04:00
[processing] Port refactor fields to new API
This commit is contained in:
parent
2364801634
commit
b3a9e46cfe
@ -40,6 +40,8 @@ class QgsProcessingOutputDefinition
|
||||
sipType = sipType_QgsProcessingOutputString;
|
||||
else if ( sipCpp->type() == QgsProcessingOutputFolder::typeName() )
|
||||
sipType = sipType_QgsProcessingOutputFolder;
|
||||
else
|
||||
sipType = nullptr;
|
||||
%End
|
||||
public:
|
||||
|
||||
|
@ -191,6 +191,8 @@ class QgsProcessingParameterDefinition
|
||||
sipType = sipType_QgsProcessingParameterFolderDestination;
|
||||
else if ( sipCpp->type() == QgsProcessingParameterBand::typeName() )
|
||||
sipType = sipType_QgsProcessingParameterBand;
|
||||
else
|
||||
sipType = nullptr;
|
||||
%End
|
||||
public:
|
||||
|
||||
|
@ -25,79 +25,76 @@ __copyright__ = '(C) 2014, Arnaud Morvan'
|
||||
|
||||
__revision__ = '$Format:%H$'
|
||||
|
||||
from qgis.core import (QgsField,
|
||||
QgsFields,
|
||||
QgsExpression,
|
||||
QgsDistanceArea,
|
||||
QgsFeatureSink,
|
||||
QgsProject,
|
||||
QgsFeature,
|
||||
QgsApplication,
|
||||
QgsProcessingUtils)
|
||||
from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm
|
||||
from processing.core.GeoAlgorithmExecutionException import GeoAlgorithmExecutionException
|
||||
from processing.core.parameters import ParameterTable
|
||||
from processing.core.parameters import Parameter
|
||||
from processing.core.outputs import OutputVector
|
||||
from qgis.core import (
|
||||
QgsApplication,
|
||||
QgsDistanceArea,
|
||||
QgsExpression,
|
||||
QgsFeature,
|
||||
QgsFeatureSink,
|
||||
QgsField,
|
||||
QgsFields,
|
||||
QgsProcessingException,
|
||||
QgsProcessingParameterDefinition,
|
||||
QgsProcessingUtils,
|
||||
QgsProject,
|
||||
)
|
||||
|
||||
from processing.algs.qgis.QgisAlgorithm import QgisFeatureBasedAlgorithm
|
||||
|
||||
|
||||
class FieldsMapper(QgisAlgorithm):
|
||||
class FieldsMapper(QgisFeatureBasedAlgorithm):
|
||||
|
||||
INPUT_LAYER = 'INPUT_LAYER'
|
||||
FIELDS_MAPPING = 'FIELDS_MAPPING'
|
||||
OUTPUT_LAYER = 'OUTPUT_LAYER'
|
||||
|
||||
def __init__(self):
|
||||
GeoAlgorithm.__init__(self)
|
||||
self.mapping = None
|
||||
|
||||
def group(self):
|
||||
return self.tr('Vector table tools')
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
def initParameters(self, config=None):
|
||||
|
||||
def initAlgorithm(self, config=None):
|
||||
self.addParameter(ParameterTable(self.INPUT_LAYER,
|
||||
self.tr('Input layer'),
|
||||
False))
|
||||
class ParameterFieldsMapping(QgsProcessingParameterDefinition):
|
||||
|
||||
class ParameterFieldsMapping(Parameter):
|
||||
def __init__(self, name, description, parentLayerParameterName='INPUT'):
|
||||
super().__init__(name, description)
|
||||
self._parentLayerParameter = parentLayerParameterName
|
||||
|
||||
default_metadata = {
|
||||
'widget_wrapper': 'processing.algs.qgis.ui.FieldsMappingPanel.FieldsMappingWidgetWrapper'
|
||||
}
|
||||
def type(self):
|
||||
return 'fields_mapping'
|
||||
|
||||
def __init__(self, name='', description='', parent=None):
|
||||
Parameter.__init__(self, name, description)
|
||||
self.parent = parent
|
||||
self.value = []
|
||||
|
||||
def getValueAsCommandLineParameter(self):
|
||||
return '"' + str(self.value) + '"'
|
||||
|
||||
def setValue(self, value):
|
||||
if value is None:
|
||||
def checkValueIsAcceptable(self, value, context):
|
||||
if not isinstance(value, list):
|
||||
return False
|
||||
if isinstance(value, list):
|
||||
self.value = value
|
||||
return True
|
||||
if isinstance(value, str):
|
||||
try:
|
||||
self.value = eval(value)
|
||||
return True
|
||||
except Exception as e:
|
||||
# fix_print_with_import
|
||||
print(str(e)) # display error in console
|
||||
for field_def in value:
|
||||
if not isinstance(field_def, dict):
|
||||
return False
|
||||
return False
|
||||
if not field_def.get('name', False):
|
||||
return False
|
||||
if not field_def.get('type', False):
|
||||
return False
|
||||
if not field_def.get('expression', False):
|
||||
return False
|
||||
return True
|
||||
|
||||
self.addParameter(ParameterFieldsMapping(self.FIELDS_MAPPING,
|
||||
self.tr('Fields mapping'),
|
||||
self.INPUT_LAYER))
|
||||
self.addOutput(OutputVector(self.OUTPUT_LAYER,
|
||||
self.tr('Refactored'),
|
||||
base_input=self.INPUT_LAYER))
|
||||
def valueAsPythonString(self, value, context):
|
||||
return str(value)
|
||||
|
||||
def asScriptCode(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
@classmethod
|
||||
def fromScriptCode(cls, name, description, isOptional, definition):
|
||||
raise NotImplementedError()
|
||||
|
||||
def parentLayerParameter(self):
|
||||
return self._parentLayerParameter
|
||||
|
||||
fields_mapping = ParameterFieldsMapping(self.FIELDS_MAPPING,
|
||||
description=self.tr('Fields mapping'))
|
||||
fields_mapping.setMetadata({
|
||||
'widget_wrapper': 'processing.algs.qgis.ui.FieldsMappingPanel.FieldsMappingWidgetWrapper'
|
||||
})
|
||||
self.addParameter(fields_mapping)
|
||||
|
||||
def name(self):
|
||||
return 'refactorfields'
|
||||
@ -105,79 +102,64 @@ class FieldsMapper(QgisAlgorithm):
|
||||
def displayName(self):
|
||||
return self.tr('Refactor fields')
|
||||
|
||||
def processAlgorithm(self, parameters, context, feedback):
|
||||
layer = self.getParameterValue(self.INPUT_LAYER)
|
||||
mapping = self.getParameterValue(self.FIELDS_MAPPING)
|
||||
output = self.getOutputFromName(self.OUTPUT_LAYER)
|
||||
def outputName(self):
|
||||
return self.tr('Refactored')
|
||||
|
||||
layer = QgsProcessingUtils.mapLayerFromString(layer, context)
|
||||
fields = QgsFields()
|
||||
expressions = []
|
||||
def parameterAsFieldsMapping(self, parameters, name, context):
|
||||
return parameters[name]
|
||||
|
||||
def prepareAlgorithm(self, parameters, context, feedback):
|
||||
source = self.parameterAsSource(parameters, 'INPUT', context)
|
||||
mapping = self.parameterAsFieldsMapping(parameters, self.FIELDS_MAPPING, context)
|
||||
|
||||
self.fields = QgsFields()
|
||||
self.expressions = []
|
||||
|
||||
da = QgsDistanceArea()
|
||||
da.setSourceCrs(layer.crs())
|
||||
da.setSourceCrs(source.sourceCrs())
|
||||
da.setEllipsoid(context.project().ellipsoid())
|
||||
|
||||
exp_context = layer.createExpressionContext()
|
||||
|
||||
for field_def in mapping:
|
||||
fields.append(QgsField(field_def['name'],
|
||||
field_def['type'],
|
||||
field_def['length'],
|
||||
field_def['precision']))
|
||||
|
||||
self.fields.append(QgsField(name=field_def['name'],
|
||||
type=field_def['type'],
|
||||
typeName="",
|
||||
len=field_def.get('length', 0),
|
||||
prec=field_def.get('precision', 0)))
|
||||
expression = QgsExpression(field_def['expression'])
|
||||
expression.setGeomCalculator(da)
|
||||
expression.setDistanceUnits(context.project().distanceUnits())
|
||||
expression.setAreaUnits(context.project().areaUnits())
|
||||
expression.prepare(exp_context)
|
||||
if expression.hasParserError():
|
||||
raise GeoAlgorithmExecutionException(
|
||||
raise QgsProcessingException(
|
||||
self.tr(u'Parser error in expression "{}": {}')
|
||||
.format(str(expression.expression()),
|
||||
str(expression.parserErrorString())))
|
||||
expressions.append(expression)
|
||||
self.expressions.append(expression)
|
||||
return True
|
||||
|
||||
writer = output.getVectorWriter(fields, layer.wkbType(), layer.crs(), context)
|
||||
def outputFields(self, inputFields):
|
||||
return self.fields
|
||||
|
||||
# Create output vector layer with new attributes
|
||||
error_exp = None
|
||||
inFeat = QgsFeature()
|
||||
outFeat = QgsFeature()
|
||||
features = QgsProcessingUtils.getFeatures(layer, context)
|
||||
count = QgsProcessingUtils.featureCount(layer, context)
|
||||
if count > 0:
|
||||
total = 100.0 / count
|
||||
for current, inFeat in enumerate(features):
|
||||
rownum = current + 1
|
||||
def processAlgorithm(self, parameters, context, feeback):
|
||||
# create an expression context using thead safe processing context
|
||||
self.expr_context = self.createExpressionContext(parameters, context)
|
||||
for expression in self.expressions:
|
||||
expression.prepare(self.expr_context)
|
||||
self._row_number = 0
|
||||
return super().processAlgorithm(parameters, context, feeback)
|
||||
|
||||
geometry = inFeat.geometry()
|
||||
outFeat.setGeometry(geometry)
|
||||
|
||||
attrs = []
|
||||
for i in range(0, len(mapping)):
|
||||
field_def = mapping[i]
|
||||
expression = expressions[i]
|
||||
exp_context.setFeature(inFeat)
|
||||
exp_context.lastScope().setVariable("row_number", rownum)
|
||||
value = expression.evaluate(exp_context)
|
||||
if expression.hasEvalError():
|
||||
error_exp = expression
|
||||
break
|
||||
|
||||
attrs.append(value)
|
||||
outFeat.setAttributes(attrs)
|
||||
|
||||
writer.addFeature(outFeat, QgsFeatureSink.FastInsert)
|
||||
|
||||
feedback.setProgress(int(current * total))
|
||||
else:
|
||||
feedback.setProgress(100)
|
||||
|
||||
del writer
|
||||
|
||||
if error_exp is not None:
|
||||
raise GeoAlgorithmExecutionException(
|
||||
self.tr(u'Evaluation error in expression "{}": {}')
|
||||
.format(str(error_exp.expression()),
|
||||
str(error_exp.parserErrorString())))
|
||||
def processFeature(self, feature, feedback):
|
||||
attributes = []
|
||||
for expression in self.expressions:
|
||||
self.expr_context.setFeature(feature)
|
||||
self.expr_context.lastScope().setVariable("row_number", self._row_number)
|
||||
value = expression.evaluate(self.expr_context)
|
||||
if expression.hasEvalError():
|
||||
raise QgsProcessingException(
|
||||
self.tr(u'Evaluation error in expression "{}": {}')
|
||||
.format(str(expression.expression()),
|
||||
str(expression.parserErrorString())))
|
||||
attributes.append(value)
|
||||
feature.setAttributes(attributes)
|
||||
self._row_number += 1
|
||||
return feature
|
||||
|
@ -160,7 +160,7 @@ from .ZonalStatistics import ZonalStatistics
|
||||
# from .SetRasterStyle import SetRasterStyle
|
||||
# from .SelectByAttributeSum import SelectByAttributeSum
|
||||
# from .HypsometricCurves import HypsometricCurves
|
||||
# from .FieldsMapper import FieldsMapper
|
||||
from .FieldsMapper import FieldsMapper
|
||||
# from .Datasources2Vrt import Datasources2Vrt
|
||||
# from .OrientedMinimumBoundingBox import OrientedMinimumBoundingBox
|
||||
# from .DefineProjection import DefineProjection
|
||||
@ -231,6 +231,7 @@ class QGISAlgorithmProvider(QgsProcessingProvider):
|
||||
ExtentFromLayer(),
|
||||
ExtractNodes(),
|
||||
ExtractSpecificNodes(),
|
||||
FieldsMapper(),
|
||||
FixedDistanceBuffer(),
|
||||
FixGeometry(),
|
||||
GeometryByExpression(),
|
||||
|
@ -31,17 +31,38 @@ import os
|
||||
from collections import OrderedDict
|
||||
|
||||
from qgis.PyQt import uic
|
||||
from qgis.PyQt.QtGui import QBrush
|
||||
from qgis.PyQt.QtWidgets import QComboBox, QHeaderView, QLineEdit, QSpacerItem, QMessageBox, QSpinBox, QStyledItemDelegate
|
||||
from qgis.PyQt.QtCore import QItemSelectionModel, QAbstractTableModel, QModelIndex, QVariant, Qt, pyqtSlot
|
||||
|
||||
from qgis.core import (QgsExpression,
|
||||
QgsProject,
|
||||
QgsApplication,
|
||||
QgsProcessingUtils)
|
||||
from qgis.PyQt.QtCore import (
|
||||
QItemSelectionModel,
|
||||
QAbstractTableModel,
|
||||
QModelIndex,
|
||||
QVariant,
|
||||
Qt,
|
||||
pyqtSlot,
|
||||
)
|
||||
from qgis.PyQt.QtGui import QBrush
|
||||
from qgis.PyQt.QtWidgets import (
|
||||
QComboBox,
|
||||
QHeaderView,
|
||||
QLineEdit,
|
||||
QSpacerItem,
|
||||
QMessageBox,
|
||||
QSpinBox,
|
||||
QStyledItemDelegate,
|
||||
)
|
||||
|
||||
from qgis.core import (
|
||||
QgsApplication,
|
||||
QgsExpression,
|
||||
QgsProcessingFeatureSourceDefinition,
|
||||
QgsProcessingUtils,
|
||||
QgsProject,
|
||||
QgsVectorLayer,
|
||||
)
|
||||
from qgis.gui import QgsFieldExpressionWidget
|
||||
|
||||
from processing.gui.wrappers import WidgetWrapper, DIALOG_STANDARD, DIALOG_MODELER
|
||||
from processing.tools import dataobjects
|
||||
|
||||
pluginPath = os.path.dirname(__file__)
|
||||
WIDGET, BASE = uic.loadUiType(
|
||||
@ -51,26 +72,49 @@ WIDGET, BASE = uic.loadUiType(
|
||||
class FieldsMappingModel(QAbstractTableModel):
|
||||
|
||||
fieldTypes = OrderedDict([
|
||||
(QVariant.Int, "Integer"),
|
||||
(QVariant.Date, "Date"),
|
||||
(QVariant.DateTime, "DateTime"),
|
||||
(QVariant.Double, "Double"),
|
||||
(QVariant.String, "String"),
|
||||
(QVariant.DateTime, "Date"),
|
||||
(QVariant.LongLong, "Double"),
|
||||
(QVariant.Date, "Date")])
|
||||
|
||||
columns = [
|
||||
{'name': 'name', 'type': QVariant.String},
|
||||
{'name': 'type', 'type': QVariant.Type},
|
||||
{'name': 'length', 'type': QVariant.Int},
|
||||
{'name': 'precision', 'type': QVariant.Int},
|
||||
# {'name': 'comment', 'type': QVariant.String},
|
||||
{'name': 'expression', 'type': QgsExpression}]
|
||||
(QVariant.Int, "Integer"),
|
||||
(QVariant.LongLong, "Integer64"),
|
||||
(QVariant.String, "String")])
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(FieldsMappingModel, self).__init__(parent)
|
||||
self._mapping = []
|
||||
self._errors = []
|
||||
self._layer = None
|
||||
self.configure()
|
||||
|
||||
def configure(self):
|
||||
self.columns = [{
|
||||
'name': 'expression',
|
||||
'type': QgsExpression,
|
||||
'header': self.tr("Source expression"),
|
||||
'persistentEditor': True
|
||||
}, {
|
||||
'name': 'name',
|
||||
'type': QVariant.String,
|
||||
'header': self.tr("Field name")
|
||||
}, {
|
||||
'name': 'type',
|
||||
'type': QVariant.Type,
|
||||
'header': self.tr("Type"),
|
||||
'persistentEditor': True
|
||||
}, {
|
||||
'name': 'length',
|
||||
'type': QVariant.Int,
|
||||
'header': self.tr("Length")
|
||||
}, {
|
||||
'name': 'precision',
|
||||
'type': QVariant.Int,
|
||||
'header': self.tr("Precision")
|
||||
}]
|
||||
|
||||
def columnIndex(self, column_name):
|
||||
for index, column in enumerate(self.columns):
|
||||
if column['name'] == column_name:
|
||||
return index
|
||||
|
||||
def mapping(self):
|
||||
return self._mapping
|
||||
@ -78,36 +122,8 @@ class FieldsMappingModel(QAbstractTableModel):
|
||||
def setMapping(self, value):
|
||||
self.beginResetModel()
|
||||
self._mapping = value
|
||||
self.testAllExpressions()
|
||||
self.endResetModel()
|
||||
|
||||
def testAllExpressions(self):
|
||||
self._errors = [None for i in range(len(self._mapping))]
|
||||
for row in range(len(self._mapping)):
|
||||
self.testExpression(row)
|
||||
|
||||
def testExpression(self, row):
|
||||
self._errors[row] = None
|
||||
field = self._mapping[row]
|
||||
exp_context = self.contextGenerator().createExpressionContext()
|
||||
|
||||
expression = QgsExpression(field['expression'])
|
||||
expression.prepare(exp_context)
|
||||
if expression.hasParserError():
|
||||
self._errors[row] = expression.parserErrorString()
|
||||
return
|
||||
|
||||
# test evaluation on the first feature
|
||||
if self._layer is None:
|
||||
return
|
||||
for feature in self._layer.getFeatures():
|
||||
exp_context.setFeature(feature)
|
||||
exp_context.lastScope().setVariable("row_number", 1)
|
||||
expression.evaluate(exp_context)
|
||||
if expression.hasEvalError():
|
||||
self._errors[row] = expression.evalErrorString()
|
||||
break
|
||||
|
||||
def contextGenerator(self):
|
||||
if self._layer:
|
||||
return self._layer
|
||||
@ -118,7 +134,6 @@ class FieldsMappingModel(QAbstractTableModel):
|
||||
|
||||
def setLayer(self, layer):
|
||||
self._layer = layer
|
||||
self.testAllExpressions()
|
||||
|
||||
def columnCount(self, parent=QModelIndex()):
|
||||
if parent.isValid():
|
||||
@ -133,70 +148,45 @@ class FieldsMappingModel(QAbstractTableModel):
|
||||
def headerData(self, section, orientation, role=Qt.DisplayRole):
|
||||
if role == Qt.DisplayRole:
|
||||
if orientation == Qt.Horizontal:
|
||||
return self.columns[section]['name'].title()
|
||||
return self.columns[section]['header']
|
||||
if orientation == Qt.Vertical:
|
||||
return section
|
||||
|
||||
def flags(self, index):
|
||||
flags = (Qt.ItemIsSelectable |
|
||||
Qt.ItemIsEditable |
|
||||
Qt.ItemIsEnabled)
|
||||
|
||||
return Qt.ItemFlags(flags)
|
||||
return Qt.ItemFlags(Qt.ItemIsSelectable |
|
||||
Qt.ItemIsEditable |
|
||||
Qt.ItemIsEnabled)
|
||||
|
||||
def data(self, index, role=Qt.DisplayRole):
|
||||
column = index.column()
|
||||
field = self._mapping[index.row()]
|
||||
column_def = self.columns[index.column()]
|
||||
|
||||
if role == Qt.DisplayRole:
|
||||
field = self._mapping[index.row()]
|
||||
column_def = self.columns[column]
|
||||
value = field[column_def['name']]
|
||||
|
||||
fieldType = column_def['type']
|
||||
if fieldType == QVariant.Type:
|
||||
if column_def['type'] == QVariant.Type:
|
||||
if value == QVariant.Invalid:
|
||||
return ''
|
||||
return self.fieldTypes[value]
|
||||
return value
|
||||
|
||||
if role == Qt.EditRole:
|
||||
field = self._mapping[index.row()]
|
||||
column_def = self.columns[column]
|
||||
value = field[column_def['name']]
|
||||
return value
|
||||
return field[column_def['name']]
|
||||
|
||||
if role == Qt.TextAlignmentRole:
|
||||
fieldType = self.columns[column]['type']
|
||||
if fieldType in [QVariant.Int]:
|
||||
if column_def['type'] in [QVariant.Int]:
|
||||
hAlign = Qt.AlignRight
|
||||
else:
|
||||
hAlign = Qt.AlignLeft
|
||||
return hAlign + Qt.AlignVCenter
|
||||
|
||||
if role == Qt.ForegroundRole:
|
||||
column_def = self.columns[column]
|
||||
if column_def['name'] == 'expression':
|
||||
brush = QBrush()
|
||||
if self._errors[index.row()]:
|
||||
brush.setColor(Qt.red)
|
||||
else:
|
||||
brush.setColor(Qt.black)
|
||||
return brush
|
||||
|
||||
if role == Qt.ToolTipRole:
|
||||
column_def = self.columns[column]
|
||||
if column_def['name'] == 'expression':
|
||||
return self._errors[index.row()]
|
||||
|
||||
def setData(self, index, value, role=Qt.EditRole):
|
||||
field = self._mapping[index.row()]
|
||||
column_def = self.columns[index.column()]
|
||||
|
||||
if role == Qt.EditRole:
|
||||
field = self._mapping[index.row()]
|
||||
column = index.column()
|
||||
column_def = self.columns[column]
|
||||
field[column_def['name']] = value
|
||||
if column_def['name'] == 'expression':
|
||||
self.testExpression(index.row())
|
||||
self.dataChanged.emit(index, index)
|
||||
|
||||
return True
|
||||
|
||||
def insertRows(self, row, count, index=QModelIndex()):
|
||||
@ -205,8 +195,6 @@ class FieldsMappingModel(QAbstractTableModel):
|
||||
for i in range(count):
|
||||
field = self.newField()
|
||||
self._mapping.insert(row + i, field)
|
||||
self._errors.insert(row + i, None)
|
||||
self.testExpression(row)
|
||||
|
||||
self.endInsertRows()
|
||||
return True
|
||||
@ -216,7 +204,6 @@ class FieldsMappingModel(QAbstractTableModel):
|
||||
|
||||
for i in range(row + count - 1, row + 1):
|
||||
self._mapping.pop(i)
|
||||
self._errors.pop(i)
|
||||
|
||||
self.endRemoveRows()
|
||||
return True
|
||||
@ -243,79 +230,57 @@ class FieldsMappingModel(QAbstractTableModel):
|
||||
dp = layer.dataProvider()
|
||||
for field in dp.fields():
|
||||
self._mapping.append(self.newField(field))
|
||||
self.testAllExpressions()
|
||||
|
||||
self.endResetModel()
|
||||
|
||||
|
||||
class FieldDelegate(QStyledItemDelegate):
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(FieldDelegate, self).__init__(parent)
|
||||
class FieldTypeDelegate(QStyledItemDelegate):
|
||||
|
||||
def createEditor(self, parent, option, index):
|
||||
column = index.column()
|
||||
editor = QComboBox(parent)
|
||||
for key, text in list(FieldsMappingModel.fieldTypes.items()):
|
||||
editor.addItem(text, key)
|
||||
return editor
|
||||
|
||||
fieldType = FieldsMappingModel.columns[column]['type']
|
||||
if fieldType == QVariant.Type:
|
||||
editor = QComboBox(parent)
|
||||
for key, text in list(FieldsMappingModel.fieldTypes.items()):
|
||||
editor.addItem(text, key)
|
||||
def setEditorData(self, editor, index):
|
||||
if not editor:
|
||||
return
|
||||
value = index.model().data(index, Qt.EditRole)
|
||||
editor.setCurrentIndex(editor.findData(value))
|
||||
|
||||
elif fieldType == QgsExpression:
|
||||
editor = QgsFieldExpressionWidget(parent)
|
||||
editor.setLayer(index.model().layer())
|
||||
editor.registerExpressionContextGenerator(index.model().contextGenerator())
|
||||
editor.fieldChanged.connect(self.on_expression_fieldChange)
|
||||
def setModelData(self, editor, model, index):
|
||||
if not editor:
|
||||
return
|
||||
value = editor.currentData()
|
||||
if value is None:
|
||||
value = QVariant.Invalid
|
||||
model.setData(index, value)
|
||||
|
||||
else:
|
||||
editor = QStyledItemDelegate.createEditor(self, parent, option, index)
|
||||
|
||||
class ExpressionDelegate(QStyledItemDelegate):
|
||||
|
||||
def createEditor(self, parent, option, index):
|
||||
editor = QgsFieldExpressionWidget(parent)
|
||||
editor.setLayer(index.model().layer())
|
||||
editor.registerExpressionContextGenerator(index.model().contextGenerator())
|
||||
editor.fieldChanged.connect(self.on_expression_fieldChange)
|
||||
editor.setAutoFillBackground(True)
|
||||
return editor
|
||||
|
||||
def setEditorData(self, editor, index):
|
||||
if not editor:
|
||||
return
|
||||
|
||||
column = index.column()
|
||||
value = index.model().data(index, Qt.EditRole)
|
||||
|
||||
fieldType = FieldsMappingModel.columns[column]['type']
|
||||
if fieldType == QVariant.Type:
|
||||
editor.setCurrentIndex(editor.findData(value))
|
||||
|
||||
elif fieldType == QgsExpression:
|
||||
editor.setField(value)
|
||||
|
||||
else:
|
||||
QStyledItemDelegate.setEditorData(self, editor, index)
|
||||
editor.setField(value)
|
||||
|
||||
def setModelData(self, editor, model, index):
|
||||
if not editor:
|
||||
return
|
||||
|
||||
column = index.column()
|
||||
|
||||
fieldType = FieldsMappingModel.columns[column]['type']
|
||||
if fieldType == QVariant.Type:
|
||||
value = editor.currentData()
|
||||
if value is None:
|
||||
value = QVariant.Invalid
|
||||
(value, isExpression, isValid) = editor.currentField()
|
||||
if isExpression is True:
|
||||
model.setData(index, value)
|
||||
|
||||
elif fieldType == QgsExpression:
|
||||
(value, isExpression, isValid) = editor.currentField()
|
||||
if isExpression is True:
|
||||
model.setData(index, value)
|
||||
else:
|
||||
model.setData(index, QgsExpression.quotedColumnRef(value))
|
||||
|
||||
else:
|
||||
QStyledItemDelegate.setModelData(self, editor, model, index)
|
||||
|
||||
def updateEditorGeometry(self, editor, option, index):
|
||||
editor.setGeometry(option.rect)
|
||||
model.setData(index, QgsExpression.quotedColumnRef(value))
|
||||
|
||||
def on_expression_fieldChange(self, fieldName):
|
||||
self.commitData.emit(self.sender())
|
||||
@ -333,27 +298,40 @@ class FieldsMappingPanel(BASE, WIDGET):
|
||||
self.downButton.setIcon(QgsApplication.getThemeIcon('/mActionArrowDown.svg'))
|
||||
self.resetButton.setIcon(QgsApplication.getThemeIcon('/mIconClearText.svg'))
|
||||
|
||||
self.model = FieldsMappingModel()
|
||||
self.fieldsView.setModel(self.model)
|
||||
self.configure()
|
||||
|
||||
self.model.modelReset.connect(self.on_model_modelReset)
|
||||
self.model.rowsInserted.connect(self.on_model_rowsInserted)
|
||||
self.fieldsView.setItemDelegate(FieldDelegate())
|
||||
|
||||
self.updateLayerCombo()
|
||||
|
||||
def configure(self):
|
||||
self.model = FieldsMappingModel()
|
||||
self.fieldsView.setModel(self.model)
|
||||
|
||||
self.setDelegate('expression', ExpressionDelegate(self))
|
||||
self.setDelegate('type', FieldTypeDelegate(self))
|
||||
|
||||
def setDelegate(self, column_name, delegate):
|
||||
self.fieldsView.setItemDelegateForColumn(
|
||||
self.model.columnIndex(column_name),
|
||||
delegate)
|
||||
|
||||
def setLayer(self, layer):
|
||||
self.model.setLayer(layer)
|
||||
if layer is None:
|
||||
return
|
||||
if self.model.rowCount() == 0:
|
||||
self.on_resetButton_clicked()
|
||||
else:
|
||||
dlg = QMessageBox(self)
|
||||
dlg.setText("Do you want to reset the field mapping?")
|
||||
dlg.setStandardButtons(
|
||||
QMessageBox.StandardButtons(QMessageBox.Yes |
|
||||
QMessageBox.No))
|
||||
dlg.setDefaultButton(QMessageBox.No)
|
||||
if dlg.exec_() == QMessageBox.Yes:
|
||||
self.on_resetButton_clicked()
|
||||
return
|
||||
dlg = QMessageBox(self)
|
||||
dlg.setText("Do you want to reset the field mapping?")
|
||||
dlg.setStandardButtons(
|
||||
QMessageBox.StandardButtons(QMessageBox.Yes |
|
||||
QMessageBox.No))
|
||||
dlg.setDefaultButton(QMessageBox.No)
|
||||
if dlg.exec_() == QMessageBox.Yes:
|
||||
self.on_resetButton_clicked()
|
||||
|
||||
def value(self):
|
||||
return self.model.mapping()
|
||||
@ -366,11 +344,13 @@ class FieldsMappingPanel(BASE, WIDGET):
|
||||
rowCount = self.model.rowCount()
|
||||
self.model.insertRows(rowCount, 1)
|
||||
index = self.model.index(rowCount, 0)
|
||||
self.fieldsView.selectionModel().select(index,
|
||||
QItemSelectionModel.SelectionFlags(QItemSelectionModel.Clear |
|
||||
QItemSelectionModel.Select |
|
||||
QItemSelectionModel.Current |
|
||||
QItemSelectionModel.Rows))
|
||||
self.fieldsView.selectionModel().select(
|
||||
index,
|
||||
QItemSelectionModel.SelectionFlags(
|
||||
QItemSelectionModel.Clear |
|
||||
QItemSelectionModel.Select |
|
||||
QItemSelectionModel.Current |
|
||||
QItemSelectionModel.Rows))
|
||||
self.fieldsView.scrollTo(index)
|
||||
self.fieldsView.scrollTo(index)
|
||||
|
||||
@ -404,11 +384,13 @@ class FieldsMappingPanel(BASE, WIDGET):
|
||||
|
||||
self.model.removeRows(row + 1, 1)
|
||||
|
||||
sel.select(self.model.index(row - 1, 0),
|
||||
QItemSelectionModel.SelectionFlags(QItemSelectionModel.Clear |
|
||||
QItemSelectionModel.Select |
|
||||
QItemSelectionModel.Current |
|
||||
QItemSelectionModel.Rows))
|
||||
sel.select(
|
||||
self.model.index(row - 1, 0),
|
||||
QItemSelectionModel.SelectionFlags(
|
||||
QItemSelectionModel.Clear |
|
||||
QItemSelectionModel.Select |
|
||||
QItemSelectionModel.Current |
|
||||
QItemSelectionModel.Rows))
|
||||
|
||||
@pyqtSlot(bool, name='on_downButton_clicked')
|
||||
def on_downButton_clicked(self, checked=False):
|
||||
@ -430,38 +412,36 @@ class FieldsMappingPanel(BASE, WIDGET):
|
||||
|
||||
self.model.removeRows(row, 1)
|
||||
|
||||
sel.select(self.model.index(row + 1, 0),
|
||||
QItemSelectionModel.SelectionFlags(QItemSelectionModel.Clear |
|
||||
QItemSelectionModel.Select |
|
||||
QItemSelectionModel.Current |
|
||||
QItemSelectionModel.Rows))
|
||||
sel.select(
|
||||
self.model.index(row + 1, 0),
|
||||
QItemSelectionModel.SelectionFlags(
|
||||
QItemSelectionModel.Clear |
|
||||
QItemSelectionModel.Select |
|
||||
QItemSelectionModel.Current |
|
||||
QItemSelectionModel.Rows))
|
||||
|
||||
@pyqtSlot(bool, name='on_resetButton_clicked')
|
||||
def on_resetButton_clicked(self, checked=False):
|
||||
self.model.loadLayerFields(self.model.layer())
|
||||
self.openPersistentEditor(
|
||||
self.model.index(0, 0),
|
||||
self.model.index(self.model.rowCount() - 1,
|
||||
self.model.columnCount() - 1))
|
||||
self.resizeColumns()
|
||||
|
||||
def resizeColumns(self):
|
||||
header = self.fieldsView.horizontalHeader()
|
||||
header.resizeSections(QHeaderView.ResizeToContents)
|
||||
for section in range(header.count()):
|
||||
size = header.sectionSize(section)
|
||||
fieldType = FieldsMappingModel.columns[section]['type']
|
||||
fieldType = self.model.columns[section]['type']
|
||||
if fieldType == QgsExpression:
|
||||
header.resizeSection(section, size + 100)
|
||||
else:
|
||||
header.resizeSection(section, size + 20)
|
||||
|
||||
def openPersistentEditor(self, topLeft, bottomRight):
|
||||
return
|
||||
for row in range(topLeft.row(), bottomRight.row() + 1):
|
||||
for column in range(topLeft.column(), bottomRight.column() + 1):
|
||||
self.fieldsView.openPersistentEditor(self.model.index(row, column))
|
||||
editor = self.fieldsView.indexWidget(self.model.index(row, column))
|
||||
def openPersistentEditors(self, row):
|
||||
for index, column in enumerate(self.model.columns):
|
||||
if 'persistentEditor' in column.keys() and column['persistentEditor']:
|
||||
self.fieldsView.openPersistentEditor(self.model.index(row, index))
|
||||
continue
|
||||
|
||||
editor = self.fieldsView.indexWidget(self.model.index(row, index))
|
||||
if isinstance(editor, QLineEdit):
|
||||
editor.deselect()
|
||||
if isinstance(editor, QSpinBox):
|
||||
@ -469,10 +449,14 @@ class FieldsMappingPanel(BASE, WIDGET):
|
||||
lineEdit.setAlignment(Qt.AlignRight or Qt.AlignVCenter)
|
||||
lineEdit.deselect()
|
||||
|
||||
def on_model_modelReset(self):
|
||||
for row in range(0, self.model.rowCount()):
|
||||
self.openPersistentEditors(row)
|
||||
self.resizeColumns()
|
||||
|
||||
def on_model_rowsInserted(self, parent, start, end):
|
||||
self.openPersistentEditor(
|
||||
self.model.index(start, 0),
|
||||
self.model.index(end, self.model.columnCount() - 1))
|
||||
for row in range(start, end + 1):
|
||||
self.openPersistentEditors(row)
|
||||
|
||||
def updateLayerCombo(self):
|
||||
layers = QgsProcessingUtils.compatibleVectorLayers(QgsProject.instance())
|
||||
@ -489,18 +473,19 @@ class FieldsMappingPanel(BASE, WIDGET):
|
||||
|
||||
class FieldsMappingWidgetWrapper(WidgetWrapper):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(FieldsMappingWidgetWrapper, self).__init__(*args, **kwargs)
|
||||
self._layer = None
|
||||
|
||||
def createWidget(self):
|
||||
return FieldsMappingPanel()
|
||||
|
||||
def postInitialize(self, wrappers):
|
||||
for wrapper in wrappers:
|
||||
if wrapper.param.name == self.param.parent:
|
||||
if wrapper.param.name() == self.param.parentLayerParameter():
|
||||
self.setLayer(wrapper.value())
|
||||
wrapper.widgetValueHasChanged.connect(self.parentLayerChanged)
|
||||
break
|
||||
layers = QgsProcessingUtils.compatibleVectorLayers(QgsProject.instance())
|
||||
if len(layers) > 0:
|
||||
# as first item in combobox is already selected
|
||||
self.widget.setLayer(layers[0])
|
||||
|
||||
# remove exiting spacers to get FieldsMappingPanel fully expanded
|
||||
if self.dialogType in (DIALOG_STANDARD, DIALOG_MODELER):
|
||||
@ -509,8 +494,21 @@ class FieldsMappingWidgetWrapper(WidgetWrapper):
|
||||
if isinstance(spacer, QSpacerItem):
|
||||
layout.removeItem(spacer)
|
||||
|
||||
def parentLayerChanged(self):
|
||||
self.widget.setLayer(self.sender().value())
|
||||
def parentLayerChanged(self, layer=None):
|
||||
self.setLayer(self.sender().value())
|
||||
|
||||
def setLayer(self, layer):
|
||||
context = dataobjects.createContext()
|
||||
if layer == self._layer:
|
||||
return
|
||||
if isinstance(layer, QgsProcessingFeatureSourceDefinition):
|
||||
layer, ok = layer.source.valueAsString(context.expressionContext())
|
||||
if isinstance(layer, str):
|
||||
layer = QgsProcessingUtils.mapLayerFromString(layer, context)
|
||||
if not isinstance(layer, QgsVectorLayer):
|
||||
layer = None
|
||||
self._layer = layer
|
||||
self.widget.setLayer(self._layer)
|
||||
|
||||
def setValue(self, value):
|
||||
self.widget.setValue(value)
|
||||
|
32
python/plugins/processing/tests/testdata/expected/refactorfields.gfs
vendored
Normal file
32
python/plugins/processing/tests/testdata/expected/refactorfields.gfs
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
<GMLFeatureClassList>
|
||||
<GMLFeatureClass>
|
||||
<Name>refactorfields</Name>
|
||||
<ElementPath>refactorfields</ElementPath>
|
||||
<!--MULTIPOLYGON-->
|
||||
<GeometryType>6</GeometryType>
|
||||
<SRSName>EPSG:4326</SRSName>
|
||||
<DatasetSpecificInfo>
|
||||
<FeatureCount>4</FeatureCount>
|
||||
<ExtentXMin>0.00000</ExtentXMin>
|
||||
<ExtentXMax>9.00000</ExtentXMax>
|
||||
<ExtentYMin>-1.00000</ExtentYMin>
|
||||
<ExtentYMax>6.00000</ExtentYMax>
|
||||
</DatasetSpecificInfo>
|
||||
<PropertyDefn>
|
||||
<Name>Bname</Name>
|
||||
<ElementPath>Bname</ElementPath>
|
||||
<Type>String</Type>
|
||||
<Width>19</Width>
|
||||
</PropertyDefn>
|
||||
<PropertyDefn>
|
||||
<Name>Bintval</Name>
|
||||
<ElementPath>Bintval</ElementPath>
|
||||
<Type>Integer</Type>
|
||||
</PropertyDefn>
|
||||
<PropertyDefn>
|
||||
<Name>Bfloatval</Name>
|
||||
<ElementPath>Bfloatval</ElementPath>
|
||||
<Type>Real</Type>
|
||||
</PropertyDefn>
|
||||
</GMLFeatureClass>
|
||||
</GMLFeatureClassList>
|
46
python/plugins/processing/tests/testdata/expected/refactorfields.gml
vendored
Normal file
46
python/plugins/processing/tests/testdata/expected/refactorfields.gml
vendored
Normal file
@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ogr:FeatureCollection
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation=""
|
||||
xmlns:ogr="http://ogr.maptools.org/"
|
||||
xmlns:gml="http://www.opengis.net/gml">
|
||||
<gml:boundedBy>
|
||||
<gml:Box>
|
||||
<gml:coord><gml:X>0</gml:X><gml:Y>-1</gml:Y></gml:coord>
|
||||
<gml:coord><gml:X>9</gml:X><gml:Y>6</gml:Y></gml:coord>
|
||||
</gml:Box>
|
||||
</gml:boundedBy>
|
||||
|
||||
<gml:featureMember>
|
||||
<ogr:refactorfields fid="refactorfields.0">
|
||||
<ogr:geometryProperty><gml:MultiPolygon srsName="EPSG:4326"><gml:polygonMember><gml:Polygon><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>2,1 2,2 3,2 3,3 4,3 4,1 2,1</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></gml:polygonMember></gml:MultiPolygon></ogr:geometryProperty>
|
||||
<ogr:row_number>0</ogr:row_number>
|
||||
<ogr:Bname>multipolys.0 - Test</ogr:Bname>
|
||||
<ogr:Bintval>2</ogr:Bintval>
|
||||
<ogr:Bfloatval>0.246</ogr:Bfloatval>
|
||||
</ogr:refactorfields>
|
||||
</gml:featureMember>
|
||||
<gml:featureMember>
|
||||
<ogr:refactorfields fid="refactorfields.1">
|
||||
<ogr:geometryProperty><gml:MultiPolygon srsName="EPSG:4326"><gml:polygonMember><gml:Polygon><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>7,-1 8,-1 8,3 7,3 7,-1</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></gml:polygonMember><gml:polygonMember><gml:Polygon><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>7,6 7,5 7,4 8,4 9,5 9,6 7,6</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></gml:polygonMember></gml:MultiPolygon></ogr:geometryProperty>
|
||||
<ogr:row_number>1</ogr:row_number>
|
||||
</ogr:refactorfields>
|
||||
</gml:featureMember>
|
||||
<gml:featureMember>
|
||||
<ogr:refactorfields fid="refactorfields.2">
|
||||
<ogr:geometryProperty><gml:MultiPolygon srsName="EPSG:4326"><gml:polygonMember><gml:Polygon><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>0,0 0,1 1,1 1,0 0,0</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></gml:polygonMember></gml:MultiPolygon></ogr:geometryProperty>
|
||||
<ogr:row_number>2</ogr:row_number>
|
||||
<ogr:Bname>multipolys.2 - Test</ogr:Bname>
|
||||
<ogr:Bintval>3</ogr:Bintval>
|
||||
<ogr:Bfloatval>-0.246</ogr:Bfloatval>
|
||||
</ogr:refactorfields>
|
||||
</gml:featureMember>
|
||||
<gml:featureMember>
|
||||
<ogr:refactorfields fid="refactorfields.3">
|
||||
<ogr:row_number>3</ogr:row_number>
|
||||
<ogr:Bname>multipolys.3 - Test</ogr:Bname>
|
||||
<ogr:Bintval>4</ogr:Bintval>
|
||||
<ogr:Bfloatval>0</ogr:Bfloatval>
|
||||
</ogr:refactorfields>
|
||||
</gml:featureMember>
|
||||
</ogr:FeatureCollection>
|
@ -2086,6 +2086,38 @@ tests:
|
||||
geometry:
|
||||
precision: 7
|
||||
|
||||
- algorithm: qgis:refactorfields
|
||||
name: refactor fields
|
||||
params:
|
||||
INPUT:
|
||||
name: multipolys.gml
|
||||
type: vector
|
||||
FIELDS_MAPPING:
|
||||
- expression: '@row_number'
|
||||
name: 'row_number'
|
||||
type: 2
|
||||
length: 0
|
||||
precision: 0
|
||||
- expression: >
|
||||
"fid" || ' - ' || "Bname"
|
||||
name: 'Bname'
|
||||
type: 10
|
||||
length: 30
|
||||
precision: 0
|
||||
- expression: '"Bintval" + 1'
|
||||
name: 'Bintval'
|
||||
type: 2
|
||||
length: 0
|
||||
precision: 0
|
||||
- expression: '"Bfloatval" * 2'
|
||||
name: 'Bfloatval'
|
||||
type: 6
|
||||
length: 0
|
||||
precision: 0
|
||||
results:
|
||||
OUTPUT:
|
||||
name: expected/refactorfields.gml
|
||||
type: vector
|
||||
|
||||
- algorithm: native:reprojectlayer
|
||||
name: reproject vector layer
|
||||
|
@ -55,6 +55,8 @@ class CORE_EXPORT QgsProcessingOutputDefinition
|
||||
sipType = sipType_QgsProcessingOutputString;
|
||||
else if ( sipCpp->type() == QgsProcessingOutputFolder::typeName() )
|
||||
sipType = sipType_QgsProcessingOutputFolder;
|
||||
else
|
||||
sipType = nullptr;
|
||||
SIP_END
|
||||
#endif
|
||||
|
||||
|
@ -233,6 +233,8 @@ class CORE_EXPORT QgsProcessingParameterDefinition
|
||||
sipType = sipType_QgsProcessingParameterFolderDestination;
|
||||
else if ( sipCpp->type() == QgsProcessingParameterBand::typeName() )
|
||||
sipType = sipType_QgsProcessingParameterBand;
|
||||
else
|
||||
sipType = nullptr;
|
||||
SIP_END
|
||||
#endif
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user