Skip to content

Commit b3a9e46

Browse files
arnaud-morvanm-kuhn
authored andcommittedAug 14, 2017
[processing] Port refactor fields to new API
1 parent 2364801 commit b3a9e46

File tree

10 files changed

+409
-310
lines changed

10 files changed

+409
-310
lines changed
 

‎python/core/processing/qgsprocessingoutputs.sip

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ class QgsProcessingOutputDefinition
4040
sipType = sipType_QgsProcessingOutputString;
4141
else if ( sipCpp->type() == QgsProcessingOutputFolder::typeName() )
4242
sipType = sipType_QgsProcessingOutputFolder;
43+
else
44+
sipType = nullptr;
4345
%End
4446
public:
4547

‎python/core/processing/qgsprocessingparameters.sip

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,8 @@ class QgsProcessingParameterDefinition
191191
sipType = sipType_QgsProcessingParameterFolderDestination;
192192
else if ( sipCpp->type() == QgsProcessingParameterBand::typeName() )
193193
sipType = sipType_QgsProcessingParameterBand;
194+
else
195+
sipType = nullptr;
194196
%End
195197
public:
196198

‎python/plugins/processing/algs/qgis/FieldsMapper.py

Lines changed: 102 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -25,159 +25,141 @@
2525

2626
__revision__ = '$Format:%H$'
2727

28-
from qgis.core import (QgsField,
29-
QgsFields,
30-
QgsExpression,
31-
QgsDistanceArea,
32-
QgsFeatureSink,
33-
QgsProject,
34-
QgsFeature,
35-
QgsApplication,
36-
QgsProcessingUtils)
37-
from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm
38-
from processing.core.GeoAlgorithmExecutionException import GeoAlgorithmExecutionException
39-
from processing.core.parameters import ParameterTable
40-
from processing.core.parameters import Parameter
41-
from processing.core.outputs import OutputVector
42-
43-
44-
class FieldsMapper(QgisAlgorithm):
28+
from qgis.core import (
29+
QgsApplication,
30+
QgsDistanceArea,
31+
QgsExpression,
32+
QgsFeature,
33+
QgsFeatureSink,
34+
QgsField,
35+
QgsFields,
36+
QgsProcessingException,
37+
QgsProcessingParameterDefinition,
38+
QgsProcessingUtils,
39+
QgsProject,
40+
)
41+
42+
from processing.algs.qgis.QgisAlgorithm import QgisFeatureBasedAlgorithm
43+
44+
45+
class FieldsMapper(QgisFeatureBasedAlgorithm):
4546

4647
INPUT_LAYER = 'INPUT_LAYER'
4748
FIELDS_MAPPING = 'FIELDS_MAPPING'
4849
OUTPUT_LAYER = 'OUTPUT_LAYER'
4950

50-
def __init__(self):
51-
GeoAlgorithm.__init__(self)
52-
self.mapping = None
53-
5451
def group(self):
5552
return self.tr('Vector table tools')
5653

57-
def __init__(self):
58-
super().__init__()
59-
60-
def initAlgorithm(self, config=None):
61-
self.addParameter(ParameterTable(self.INPUT_LAYER,
62-
self.tr('Input layer'),
63-
False))
54+
def initParameters(self, config=None):
6455

65-
class ParameterFieldsMapping(Parameter):
56+
class ParameterFieldsMapping(QgsProcessingParameterDefinition):
6657

67-
default_metadata = {
68-
'widget_wrapper': 'processing.algs.qgis.ui.FieldsMappingPanel.FieldsMappingWidgetWrapper'
69-
}
58+
def __init__(self, name, description, parentLayerParameterName='INPUT'):
59+
super().__init__(name, description)
60+
self._parentLayerParameter = parentLayerParameterName
7061

71-
def __init__(self, name='', description='', parent=None):
72-
Parameter.__init__(self, name, description)
73-
self.parent = parent
74-
self.value = []
62+
def type(self):
63+
return 'fields_mapping'
7564

76-
def getValueAsCommandLineParameter(self):
77-
return '"' + str(self.value) + '"'
78-
79-
def setValue(self, value):
80-
if value is None:
65+
def checkValueIsAcceptable(self, value, context):
66+
if not isinstance(value, list):
8167
return False
82-
if isinstance(value, list):
83-
self.value = value
84-
return True
85-
if isinstance(value, str):
86-
try:
87-
self.value = eval(value)
88-
return True
89-
except Exception as e:
90-
# fix_print_with_import
91-
print(str(e)) # display error in console
68+
for field_def in value:
69+
if not isinstance(field_def, dict):
70+
return False
71+
if not field_def.get('name', False):
72+
return False
73+
if not field_def.get('type', False):
74+
return False
75+
if not field_def.get('expression', False):
9276
return False
93-
return False
77+
return True
9478

95-
self.addParameter(ParameterFieldsMapping(self.FIELDS_MAPPING,
96-
self.tr('Fields mapping'),
97-
self.INPUT_LAYER))
98-
self.addOutput(OutputVector(self.OUTPUT_LAYER,
99-
self.tr('Refactored'),
100-
base_input=self.INPUT_LAYER))
79+
def valueAsPythonString(self, value, context):
80+
return str(value)
81+
82+
def asScriptCode(self):
83+
raise NotImplementedError()
84+
85+
@classmethod
86+
def fromScriptCode(cls, name, description, isOptional, definition):
87+
raise NotImplementedError()
88+
89+
def parentLayerParameter(self):
90+
return self._parentLayerParameter
91+
92+
fields_mapping = ParameterFieldsMapping(self.FIELDS_MAPPING,
93+
description=self.tr('Fields mapping'))
94+
fields_mapping.setMetadata({
95+
'widget_wrapper': 'processing.algs.qgis.ui.FieldsMappingPanel.FieldsMappingWidgetWrapper'
96+
})
97+
self.addParameter(fields_mapping)
10198

10299
def name(self):
103100
return 'refactorfields'
104101

105102
def displayName(self):
106103
return self.tr('Refactor fields')
107104

108-
def processAlgorithm(self, parameters, context, feedback):
109-
layer = self.getParameterValue(self.INPUT_LAYER)
110-
mapping = self.getParameterValue(self.FIELDS_MAPPING)
111-
output = self.getOutputFromName(self.OUTPUT_LAYER)
105+
def outputName(self):
106+
return self.tr('Refactored')
112107

113-
layer = QgsProcessingUtils.mapLayerFromString(layer, context)
114-
fields = QgsFields()
115-
expressions = []
108+
def parameterAsFieldsMapping(self, parameters, name, context):
109+
return parameters[name]
110+
111+
def prepareAlgorithm(self, parameters, context, feedback):
112+
source = self.parameterAsSource(parameters, 'INPUT', context)
113+
mapping = self.parameterAsFieldsMapping(parameters, self.FIELDS_MAPPING, context)
114+
115+
self.fields = QgsFields()
116+
self.expressions = []
116117

117118
da = QgsDistanceArea()
118-
da.setSourceCrs(layer.crs())
119+
da.setSourceCrs(source.sourceCrs())
119120
da.setEllipsoid(context.project().ellipsoid())
120121

121-
exp_context = layer.createExpressionContext()
122-
123122
for field_def in mapping:
124-
fields.append(QgsField(field_def['name'],
125-
field_def['type'],
126-
field_def['length'],
127-
field_def['precision']))
128-
123+
self.fields.append(QgsField(name=field_def['name'],
124+
type=field_def['type'],
125+
typeName="",
126+
len=field_def.get('length', 0),
127+
prec=field_def.get('precision', 0)))
129128
expression = QgsExpression(field_def['expression'])
130129
expression.setGeomCalculator(da)
131130
expression.setDistanceUnits(context.project().distanceUnits())
132131
expression.setAreaUnits(context.project().areaUnits())
133-
expression.prepare(exp_context)
134132
if expression.hasParserError():
135-
raise GeoAlgorithmExecutionException(
133+
raise QgsProcessingException(
136134
self.tr(u'Parser error in expression "{}": {}')
137135
.format(str(expression.expression()),
138136
str(expression.parserErrorString())))
139-
expressions.append(expression)
140-
141-
writer = output.getVectorWriter(fields, layer.wkbType(), layer.crs(), context)
142-
143-
# Create output vector layer with new attributes
144-
error_exp = None
145-
inFeat = QgsFeature()
146-
outFeat = QgsFeature()
147-
features = QgsProcessingUtils.getFeatures(layer, context)
148-
count = QgsProcessingUtils.featureCount(layer, context)
149-
if count > 0:
150-
total = 100.0 / count
151-
for current, inFeat in enumerate(features):
152-
rownum = current + 1
153-
154-
geometry = inFeat.geometry()
155-
outFeat.setGeometry(geometry)
156-
157-
attrs = []
158-
for i in range(0, len(mapping)):
159-
field_def = mapping[i]
160-
expression = expressions[i]
161-
exp_context.setFeature(inFeat)
162-
exp_context.lastScope().setVariable("row_number", rownum)
163-
value = expression.evaluate(exp_context)
164-
if expression.hasEvalError():
165-
error_exp = expression
166-
break
167-
168-
attrs.append(value)
169-
outFeat.setAttributes(attrs)
170-
171-
writer.addFeature(outFeat, QgsFeatureSink.FastInsert)
172-
173-
feedback.setProgress(int(current * total))
174-
else:
175-
feedback.setProgress(100)
176-
177-
del writer
178-
179-
if error_exp is not None:
180-
raise GeoAlgorithmExecutionException(
181-
self.tr(u'Evaluation error in expression "{}": {}')
182-
.format(str(error_exp.expression()),
183-
str(error_exp.parserErrorString())))
137+
self.expressions.append(expression)
138+
return True
139+
140+
def outputFields(self, inputFields):
141+
return self.fields
142+
143+
def processAlgorithm(self, parameters, context, feeback):
144+
# create an expression context using thead safe processing context
145+
self.expr_context = self.createExpressionContext(parameters, context)
146+
for expression in self.expressions:
147+
expression.prepare(self.expr_context)
148+
self._row_number = 0
149+
return super().processAlgorithm(parameters, context, feeback)
150+
151+
def processFeature(self, feature, feedback):
152+
attributes = []
153+
for expression in self.expressions:
154+
self.expr_context.setFeature(feature)
155+
self.expr_context.lastScope().setVariable("row_number", self._row_number)
156+
value = expression.evaluate(self.expr_context)
157+
if expression.hasEvalError():
158+
raise QgsProcessingException(
159+
self.tr(u'Evaluation error in expression "{}": {}')
160+
.format(str(expression.expression()),
161+
str(expression.parserErrorString())))
162+
attributes.append(value)
163+
feature.setAttributes(attributes)
164+
self._row_number += 1
165+
return feature

‎python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@
160160
# from .SetRasterStyle import SetRasterStyle
161161
# from .SelectByAttributeSum import SelectByAttributeSum
162162
# from .HypsometricCurves import HypsometricCurves
163-
# from .FieldsMapper import FieldsMapper
163+
from .FieldsMapper import FieldsMapper
164164
# from .Datasources2Vrt import Datasources2Vrt
165165
# from .OrientedMinimumBoundingBox import OrientedMinimumBoundingBox
166166
# from .DefineProjection import DefineProjection
@@ -231,6 +231,7 @@ def getAlgs(self):
231231
ExtentFromLayer(),
232232
ExtractNodes(),
233233
ExtractSpecificNodes(),
234+
FieldsMapper(),
234235
FixedDistanceBuffer(),
235236
FixGeometry(),
236237
GeometryByExpression(),

‎python/plugins/processing/algs/qgis/ui/FieldsMappingPanel.py

Lines changed: 187 additions & 189 deletions
Large diffs are not rendered by default.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<GMLFeatureClassList>
2+
<GMLFeatureClass>
3+
<Name>refactorfields</Name>
4+
<ElementPath>refactorfields</ElementPath>
5+
<!--MULTIPOLYGON-->
6+
<GeometryType>6</GeometryType>
7+
<SRSName>EPSG:4326</SRSName>
8+
<DatasetSpecificInfo>
9+
<FeatureCount>4</FeatureCount>
10+
<ExtentXMin>0.00000</ExtentXMin>
11+
<ExtentXMax>9.00000</ExtentXMax>
12+
<ExtentYMin>-1.00000</ExtentYMin>
13+
<ExtentYMax>6.00000</ExtentYMax>
14+
</DatasetSpecificInfo>
15+
<PropertyDefn>
16+
<Name>Bname</Name>
17+
<ElementPath>Bname</ElementPath>
18+
<Type>String</Type>
19+
<Width>19</Width>
20+
</PropertyDefn>
21+
<PropertyDefn>
22+
<Name>Bintval</Name>
23+
<ElementPath>Bintval</ElementPath>
24+
<Type>Integer</Type>
25+
</PropertyDefn>
26+
<PropertyDefn>
27+
<Name>Bfloatval</Name>
28+
<ElementPath>Bfloatval</ElementPath>
29+
<Type>Real</Type>
30+
</PropertyDefn>
31+
</GMLFeatureClass>
32+
</GMLFeatureClassList>
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<ogr:FeatureCollection
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation=""
5+
xmlns:ogr="http://ogr.maptools.org/"
6+
xmlns:gml="http://www.opengis.net/gml">
7+
<gml:boundedBy>
8+
<gml:Box>
9+
<gml:coord><gml:X>0</gml:X><gml:Y>-1</gml:Y></gml:coord>
10+
<gml:coord><gml:X>9</gml:X><gml:Y>6</gml:Y></gml:coord>
11+
</gml:Box>
12+
</gml:boundedBy>
13+
14+
<gml:featureMember>
15+
<ogr:refactorfields fid="refactorfields.0">
16+
<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>
17+
<ogr:row_number>0</ogr:row_number>
18+
<ogr:Bname>multipolys.0 - Test</ogr:Bname>
19+
<ogr:Bintval>2</ogr:Bintval>
20+
<ogr:Bfloatval>0.246</ogr:Bfloatval>
21+
</ogr:refactorfields>
22+
</gml:featureMember>
23+
<gml:featureMember>
24+
<ogr:refactorfields fid="refactorfields.1">
25+
<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>
26+
<ogr:row_number>1</ogr:row_number>
27+
</ogr:refactorfields>
28+
</gml:featureMember>
29+
<gml:featureMember>
30+
<ogr:refactorfields fid="refactorfields.2">
31+
<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>
32+
<ogr:row_number>2</ogr:row_number>
33+
<ogr:Bname>multipolys.2 - Test</ogr:Bname>
34+
<ogr:Bintval>3</ogr:Bintval>
35+
<ogr:Bfloatval>-0.246</ogr:Bfloatval>
36+
</ogr:refactorfields>
37+
</gml:featureMember>
38+
<gml:featureMember>
39+
<ogr:refactorfields fid="refactorfields.3">
40+
<ogr:row_number>3</ogr:row_number>
41+
<ogr:Bname>multipolys.3 - Test</ogr:Bname>
42+
<ogr:Bintval>4</ogr:Bintval>
43+
<ogr:Bfloatval>0</ogr:Bfloatval>
44+
</ogr:refactorfields>
45+
</gml:featureMember>
46+
</ogr:FeatureCollection>

‎python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2086,6 +2086,38 @@ tests:
20862086
geometry:
20872087
precision: 7
20882088

2089+
- algorithm: qgis:refactorfields
2090+
name: refactor fields
2091+
params:
2092+
INPUT:
2093+
name: multipolys.gml
2094+
type: vector
2095+
FIELDS_MAPPING:
2096+
- expression: '@row_number'
2097+
name: 'row_number'
2098+
type: 2
2099+
length: 0
2100+
precision: 0
2101+
- expression: >
2102+
"fid" || ' - ' || "Bname"
2103+
name: 'Bname'
2104+
type: 10
2105+
length: 30
2106+
precision: 0
2107+
- expression: '"Bintval" + 1'
2108+
name: 'Bintval'
2109+
type: 2
2110+
length: 0
2111+
precision: 0
2112+
- expression: '"Bfloatval" * 2'
2113+
name: 'Bfloatval'
2114+
type: 6
2115+
length: 0
2116+
precision: 0
2117+
results:
2118+
OUTPUT:
2119+
name: expected/refactorfields.gml
2120+
type: vector
20892121

20902122
- algorithm: native:reprojectlayer
20912123
name: reproject vector layer

‎src/core/processing/qgsprocessingoutputs.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ class CORE_EXPORT QgsProcessingOutputDefinition
5555
sipType = sipType_QgsProcessingOutputString;
5656
else if ( sipCpp->type() == QgsProcessingOutputFolder::typeName() )
5757
sipType = sipType_QgsProcessingOutputFolder;
58+
else
59+
sipType = nullptr;
5860
SIP_END
5961
#endif
6062

‎src/core/processing/qgsprocessingparameters.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,8 @@ class CORE_EXPORT QgsProcessingParameterDefinition
233233
sipType = sipType_QgsProcessingParameterFolderDestination;
234234
else if ( sipCpp->type() == QgsProcessingParameterBand::typeName() )
235235
sipType = sipType_QgsProcessingParameterBand;
236+
else
237+
sipType = nullptr;
236238
SIP_END
237239
#endif
238240

0 commit comments

Comments
 (0)
Please sign in to comment.