Skip to content

Commit f65e770

Browse files
committedNov 2, 2016
[FEATURE[processing] New algorithm to compute geometry by expression
This algorithm updates existing geometries (or creates new geometries) for input features by use of a QGIS expression. This allows complex geometry modifications which can utilise all the flexibility of the QGIS expression engine to manipulate and create geometries for output features.
1 parent 5e3bef7 commit f65e770

10 files changed

+411
-2
lines changed
 

‎python/plugins/processing/algs/help/qgis.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,11 @@ qgis:fixeddistancebuffer: >
201201
qgis:frequencyanalysis: >
202202
This algorithms generates a table with frequency analysis of the values of a selected attribute from an input vector layer
203203

204+
qgis:geometrybyexpression: >
205+
This algorithm updates existing geometries (or creates new geometries) for input features by use of a QGIS expression. This allows complex geometry modifications which can utilise all the flexibility of the QGIS expression engine to manipulate and create geometries for output features.
206+
207+
For help with QGIS expression functions, see the inbuilt help for specific functions which is available in the expression builder.
208+
204209
qgis:generatepointspixelcentroidsalongline:
205210

206211

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
# -*- coding: utf-8 -*-
2+
3+
"""
4+
***************************************************************************
5+
GeometryByExpression.py
6+
-----------------------
7+
Date : October 2016
8+
Copyright : (C) 2016 by Nyall Dawson
9+
Email : nyall dot dawson at gmail dot com
10+
***************************************************************************
11+
* *
12+
* This program is free software; you can redistribute it and/or modify *
13+
* it under the terms of the GNU General Public License as published by *
14+
* the Free Software Foundation; either version 2 of the License, or *
15+
* (at your option) any later version. *
16+
* *
17+
***************************************************************************
18+
"""
19+
20+
__author__ = 'Nyall Dawson'
21+
__date__ = 'October 2016'
22+
__copyright__ = '(C) 2016, Nyall Dawson'
23+
24+
# This will get replaced with a git SHA1 when you do a git archive323
25+
26+
__revision__ = '$Format:%H$'
27+
28+
from qgis.core import QgsWkbTypes, QgsExpression, QgsExpressionContext, QgsExpressionContextUtils, QgsGeometry
29+
30+
from processing.core.GeoAlgorithm import GeoAlgorithm
31+
from processing.core.GeoAlgorithmExecutionException import GeoAlgorithmExecutionException
32+
from processing.core.ProcessingLog import ProcessingLog
33+
from processing.core.parameters import ParameterVector, ParameterSelection, ParameterBoolean, ParameterString
34+
from processing.core.outputs import OutputVector
35+
from processing.tools import dataobjects, vector
36+
37+
38+
class GeometryByExpression(GeoAlgorithm):
39+
40+
INPUT_LAYER = 'INPUT_LAYER'
41+
OUTPUT_LAYER = 'OUTPUT_LAYER'
42+
OUTPUT_GEOMETRY = 'OUTPUT_GEOMETRY'
43+
WITH_Z = 'WITH_Z'
44+
WITH_M = 'WITH_M'
45+
EXPRESSION = 'EXPRESSION'
46+
47+
def defineCharacteristics(self):
48+
self.name, self.i18n_name = self.trAlgorithm('Geometry by expression')
49+
self.group, self.i18n_group = self.trAlgorithm('Vector geometry tools')
50+
51+
self.addParameter(ParameterVector(self.INPUT_LAYER,
52+
self.tr('Input layer')))
53+
54+
self.geometry_types = [self.tr('Polygon'),
55+
'Line',
56+
'Point']
57+
self.addParameter(ParameterSelection(
58+
self.OUTPUT_GEOMETRY,
59+
self.tr('Output geometry type'),
60+
self.geometry_types, default=0))
61+
self.addParameter(ParameterBoolean(self.WITH_Z,
62+
self.tr('Output geometry has z dimension'), False))
63+
self.addParameter(ParameterBoolean(self.WITH_M,
64+
self.tr('Output geometry has m values'), False))
65+
66+
self.addParameter(ParameterString(self.EXPRESSION,
67+
self.tr("Geometry expression"), '$geometry'))
68+
69+
self.addOutput(OutputVector(self.OUTPUT_LAYER, self.tr('Modified geometry')))
70+
71+
def processAlgorithm(self, progress):
72+
layer = dataobjects.getObjectFromUri(
73+
self.getParameterValue(self.INPUT_LAYER))
74+
75+
geometry_type = self.getParameterValue(self.OUTPUT_GEOMETRY)
76+
wkb_type = None
77+
if geometry_type == 0:
78+
wkb_type = QgsWkbTypes.Polygon
79+
elif geometry_type == 1:
80+
wkb_type = QgsWkbTypes.LineString
81+
else:
82+
wkb_type = QgsWkbTypes.Point
83+
if self.getParameterValue(self.WITH_Z):
84+
wkb_type = QgsWkbTypes.addZ(wkb_type)
85+
if self.getParameterValue(self.WITH_M):
86+
wkb_type = QgsWkbTypes.addM(wkb_type)
87+
88+
writer = self.getOutputFromName(
89+
self.OUTPUT_LAYER).getVectorWriter(
90+
layer.fields(),
91+
wkb_type,
92+
layer.crs())
93+
94+
expression = QgsExpression(self.getParameterValue(self.EXPRESSION))
95+
if expression.hasParserError():
96+
raise GeoAlgorithmExecutionException(expression.parserErrorString())
97+
98+
exp_context = QgsExpressionContext()
99+
exp_context.appendScope(QgsExpressionContextUtils.globalScope())
100+
exp_context.appendScope(QgsExpressionContextUtils.projectScope())
101+
exp_context.appendScope(QgsExpressionContextUtils.layerScope(layer))
102+
103+
if not expression.prepare(exp_context):
104+
raise GeoAlgorithmExecutionException(
105+
self.tr('Evaluation error: %s' % expression.evalErrorString()))
106+
107+
features = vector.features(layer)
108+
total = 100.0 / len(features)
109+
for current, input_feature in enumerate(features):
110+
output_feature = input_feature
111+
112+
exp_context.setFeature(input_feature)
113+
value = expression.evaluate(exp_context)
114+
if expression.hasEvalError():
115+
raise GeoAlgorithmExecutionException(
116+
self.tr('Evaluation error: %s' % expression.evalErrorString()))
117+
118+
if not value:
119+
output_feature.setGeometry(QgsGeometry())
120+
else:
121+
if not isinstance(value, QgsGeometry):
122+
raise GeoAlgorithmExecutionException(
123+
self.tr('{} is not a geometry').format(value))
124+
output_feature.setGeometry(value)
125+
126+
writer.addFeature(output_feature)
127+
progress.setPercentage(int(current * total))
128+
129+
del writer

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@
174174
from .RemoveNullGeometry import RemoveNullGeometry
175175
from .ExtendLines import ExtendLines
176176
from .ExtractSpecificNodes import ExtractSpecificNodes
177+
from .GeometryByExpression import GeometryByExpression
177178

178179
pluginPath = os.path.normpath(os.path.join(
179180
os.path.split(os.path.dirname(__file__))[0], os.pardir))
@@ -236,7 +237,7 @@ def __init__(self):
236237
IdwInterpolationZValue(), IdwInterpolationAttribute(),
237238
TinInterpolationZValue(), TinInterpolationAttribute(),
238239
RemoveNullGeometry(), ExtractByExpression(), ExtendLines(),
239-
ExtractSpecificNodes()
240+
ExtractSpecificNodes(), GeometryByExpression()
240241
]
241242

242243
if hasMatplotlib:
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<GMLFeatureClassList>
2+
<GMLFeatureClass>
3+
<Name>geometry_by_expression_line</Name>
4+
<ElementPath>geometry_by_expression_line</ElementPath>
5+
<!--LINESTRING-->
6+
<GeometryType>2</GeometryType>
7+
<SRSName>EPSG:4326</SRSName>
8+
<DatasetSpecificInfo>
9+
<FeatureCount>7</FeatureCount>
10+
<ExtentXMin>0.00000</ExtentXMin>
11+
<ExtentXMax>12.00000</ExtentXMax>
12+
<ExtentYMin>-2.00000</ExtentYMin>
13+
<ExtentYMax>6.00000</ExtentYMax>
14+
</DatasetSpecificInfo>
15+
</GMLFeatureClass>
16+
</GMLFeatureClassList>
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
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>-2</gml:Y></gml:coord>
10+
<gml:coord><gml:X>12</gml:X><gml:Y>6</gml:Y></gml:coord>
11+
</gml:Box>
12+
</gml:boundedBy>
13+
14+
<gml:featureMember>
15+
<ogr:geometry_by_expression_line fid="lines.0">
16+
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>7,3 10,3 10,4 12,6</gml:coordinates></gml:LineString></ogr:geometryProperty>
17+
</ogr:geometry_by_expression_line>
18+
</gml:featureMember>
19+
<gml:featureMember>
20+
<ogr:geometry_by_expression_line fid="lines.1">
21+
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>0,0 2,0</gml:coordinates></gml:LineString></ogr:geometryProperty>
22+
</ogr:geometry_by_expression_line>
23+
</gml:featureMember>
24+
<gml:featureMember>
25+
<ogr:geometry_by_expression_line fid="lines.2">
26+
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>3,1 3,3 4,3 4,4</gml:coordinates></gml:LineString></ogr:geometryProperty>
27+
</ogr:geometry_by_expression_line>
28+
</gml:featureMember>
29+
<gml:featureMember>
30+
<ogr:geometry_by_expression_line fid="lines.3">
31+
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>4,2 6,2</gml:coordinates></gml:LineString></ogr:geometryProperty>
32+
</ogr:geometry_by_expression_line>
33+
</gml:featureMember>
34+
<gml:featureMember>
35+
<ogr:geometry_by_expression_line fid="lines.4">
36+
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>8,-2 11,-2</gml:coordinates></gml:LineString></ogr:geometryProperty>
37+
</ogr:geometry_by_expression_line>
38+
</gml:featureMember>
39+
<gml:featureMember>
40+
<ogr:geometry_by_expression_line fid="lines.5">
41+
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>7,-2 11,2</gml:coordinates></gml:LineString></ogr:geometryProperty>
42+
</ogr:geometry_by_expression_line>
43+
</gml:featureMember>
44+
<gml:featureMember>
45+
<ogr:geometry_by_expression_line fid="lines.6">
46+
</ogr:geometry_by_expression_line>
47+
</gml:featureMember>
48+
</ogr:FeatureCollection>
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<GMLFeatureClassList>
2+
<GMLFeatureClass>
3+
<Name>geometry_by_expression_point</Name>
4+
<ElementPath>geometry_by_expression_point</ElementPath>
5+
<!--POINT-->
6+
<GeometryType>1</GeometryType>
7+
<SRSName>EPSG:4326</SRSName>
8+
<DatasetSpecificInfo>
9+
<FeatureCount>9</FeatureCount>
10+
<ExtentXMin>1.00000</ExtentXMin>
11+
<ExtentXMax>9.00000</ExtentXMax>
12+
<ExtentYMin>-4.00000</ExtentYMin>
13+
<ExtentYMax>4.00000</ExtentYMax>
14+
</DatasetSpecificInfo>
15+
</GMLFeatureClass>
16+
</GMLFeatureClassList>
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
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>1</gml:X><gml:Y>-4</gml:Y></gml:coord>
10+
<gml:coord><gml:X>9</gml:X><gml:Y>4</gml:Y></gml:coord>
11+
</gml:Box>
12+
</gml:boundedBy>
13+
14+
<gml:featureMember>
15+
<ogr:geometry_by_expression_point fid="points.0">
16+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>2,2</gml:coordinates></gml:Point></ogr:geometryProperty>
17+
</ogr:geometry_by_expression_point>
18+
</gml:featureMember>
19+
<gml:featureMember>
20+
<ogr:geometry_by_expression_point fid="points.1">
21+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>4,4</gml:coordinates></gml:Point></ogr:geometryProperty>
22+
</ogr:geometry_by_expression_point>
23+
</gml:featureMember>
24+
<gml:featureMember>
25+
<ogr:geometry_by_expression_point fid="points.2">
26+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>3,3</gml:coordinates></gml:Point></ogr:geometryProperty>
27+
</ogr:geometry_by_expression_point>
28+
</gml:featureMember>
29+
<gml:featureMember>
30+
<ogr:geometry_by_expression_point fid="points.3">
31+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>6,3</gml:coordinates></gml:Point></ogr:geometryProperty>
32+
</ogr:geometry_by_expression_point>
33+
</gml:featureMember>
34+
<gml:featureMember>
35+
<ogr:geometry_by_expression_point fid="points.4">
36+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>5,2</gml:coordinates></gml:Point></ogr:geometryProperty>
37+
</ogr:geometry_by_expression_point>
38+
</gml:featureMember>
39+
<gml:featureMember>
40+
<ogr:geometry_by_expression_point fid="points.5">
41+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>1,-4</gml:coordinates></gml:Point></ogr:geometryProperty>
42+
</ogr:geometry_by_expression_point>
43+
</gml:featureMember>
44+
<gml:featureMember>
45+
<ogr:geometry_by_expression_point fid="points.6">
46+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>9,0</gml:coordinates></gml:Point></ogr:geometryProperty>
47+
</ogr:geometry_by_expression_point>
48+
</gml:featureMember>
49+
<gml:featureMember>
50+
<ogr:geometry_by_expression_point fid="points.7">
51+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>8,0</gml:coordinates></gml:Point></ogr:geometryProperty>
52+
</ogr:geometry_by_expression_point>
53+
</gml:featureMember>
54+
<gml:featureMember>
55+
<ogr:geometry_by_expression_point fid="points.8">
56+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>1,0</gml:coordinates></gml:Point></ogr:geometryProperty>
57+
</ogr:geometry_by_expression_point>
58+
</gml:featureMember>
59+
</ogr:FeatureCollection>
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<GMLFeatureClassList>
2+
<GMLFeatureClass>
3+
<Name>geometry_by_expression_poly</Name>
4+
<ElementPath>geometry_by_expression_poly</ElementPath>
5+
<!--POINT-->
6+
<GeometryType>1</GeometryType>
7+
<SRSName>EPSG:4326</SRSName>
8+
<DatasetSpecificInfo>
9+
<FeatureCount>6</FeatureCount>
10+
<ExtentXMin>1.65385</ExtentXMin>
11+
<ExtentXMax>9.00000</ExtentXMax>
12+
<ExtentYMin>0.00000</ExtentYMin>
13+
<ExtentYMax>6.50000</ExtentYMax>
14+
</DatasetSpecificInfo>
15+
<PropertyDefn>
16+
<Name>name</Name>
17+
<ElementPath>name</ElementPath>
18+
<Type>String</Type>
19+
<Width>5</Width>
20+
</PropertyDefn>
21+
<PropertyDefn>
22+
<Name>intval</Name>
23+
<ElementPath>intval</ElementPath>
24+
<Type>Integer</Type>
25+
</PropertyDefn>
26+
<PropertyDefn>
27+
<Name>floatval</Name>
28+
<ElementPath>floatval</ElementPath>
29+
<Type>Real</Type>
30+
</PropertyDefn>
31+
</GMLFeatureClass>
32+
</GMLFeatureClassList>
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
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>1.653846153846154</gml:X><gml:Y>0</gml:Y></gml:coord>
10+
<gml:coord><gml:X>9</gml:X><gml:Y>6.5</gml:Y></gml:coord>
11+
</gml:Box>
12+
</gml:boundedBy>
13+
14+
<gml:featureMember>
15+
<ogr:geometry_by_expression_poly fid="polys.0">
16+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>1.653846153846154,2.115384615384615</gml:coordinates></gml:Point></ogr:geometryProperty>
17+
<ogr:name>aaaaa</ogr:name>
18+
<ogr:intval>33</ogr:intval>
19+
<ogr:floatval>44.123456</ogr:floatval>
20+
</ogr:geometry_by_expression_poly>
21+
</gml:featureMember>
22+
<gml:featureMember>
23+
<ogr:geometry_by_expression_poly fid="polys.1">
24+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>6.0,5.333333333333333</gml:coordinates></gml:Point></ogr:geometryProperty>
25+
<ogr:name>Aaaaa</ogr:name>
26+
<ogr:intval>-33</ogr:intval>
27+
<ogr:floatval>0</ogr:floatval>
28+
</ogr:geometry_by_expression_poly>
29+
</gml:featureMember>
30+
<gml:featureMember>
31+
<ogr:geometry_by_expression_poly fid="polys.2">
32+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>3.5,6.5</gml:coordinates></gml:Point></ogr:geometryProperty>
33+
<ogr:name>bbaaa</ogr:name>
34+
<ogr:floatval>0.123</ogr:floatval>
35+
</ogr:geometry_by_expression_poly>
36+
</gml:featureMember>
37+
<gml:featureMember>
38+
<ogr:geometry_by_expression_poly fid="polys.3">
39+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>9,0</gml:coordinates></gml:Point></ogr:geometryProperty>
40+
<ogr:name>ASDF</ogr:name>
41+
<ogr:intval>0</ogr:intval>
42+
</ogr:geometry_by_expression_poly>
43+
</gml:featureMember>
44+
<gml:featureMember>
45+
<ogr:geometry_by_expression_poly fid="polys.4">
46+
<ogr:intval>120</ogr:intval>
47+
<ogr:floatval>-100291.43213</ogr:floatval>
48+
</ogr:geometry_by_expression_poly>
49+
</gml:featureMember>
50+
<gml:featureMember>
51+
<ogr:geometry_by_expression_poly fid="polys.5">
52+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>5.080459770114943,0.781609195402299</gml:coordinates></gml:Point></ogr:geometryProperty>
53+
<ogr:name>elim</ogr:name>
54+
<ogr:intval>2</ogr:intval>
55+
<ogr:floatval>3.33</ogr:floatval>
56+
</ogr:geometry_by_expression_poly>
57+
</gml:featureMember>
58+
</ogr:FeatureCollection>

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

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1275,4 +1275,49 @@ tests:
12751275
results:
12761276
OUTPUT_LAYER:
12771277
name: expected/extract_specific_nodes_polys.gml
1278-
type: vector
1278+
type: vector
1279+
1280+
- algorithm: qgis:geometrybyexpression
1281+
name: Geometry by expression (point)
1282+
params:
1283+
EXPRESSION: 'translate( $geometry,1,1)'
1284+
INPUT_LAYER:
1285+
name: points.gml
1286+
type: vector
1287+
OUTPUT_GEOMETRY: '0'
1288+
WITH_M: false
1289+
WITH_Z: false
1290+
results:
1291+
OUTPUT_LAYER:
1292+
name: expected/geometry_by_expression_point.gml
1293+
type: vector
1294+
1295+
- algorithm: qgis:geometrybyexpression
1296+
name: Geometry by expression (polygon)
1297+
params:
1298+
EXPRESSION: ' translate( centroid($geometry),1,1)'
1299+
INPUT_LAYER:
1300+
name: polys.gml
1301+
type: vector
1302+
OUTPUT_GEOMETRY: '2'
1303+
WITH_M: false
1304+
WITH_Z: false
1305+
results:
1306+
OUTPUT_LAYER:
1307+
name: expected/geometry_by_expression_poly.gml
1308+
type: vector
1309+
1310+
- algorithm: qgis:geometrybyexpression
1311+
name: Geometry by expression (line)
1312+
params:
1313+
EXPRESSION: ' translate( $geometry,1,1)'
1314+
INPUT_LAYER:
1315+
name: lines.gml
1316+
type: vector
1317+
OUTPUT_GEOMETRY: '1'
1318+
WITH_M: false
1319+
WITH_Z: false
1320+
results:
1321+
OUTPUT_LAYER:
1322+
name: expected/geometry_by_expression_line.gml
1323+
type: vector

0 commit comments

Comments
 (0)
Please sign in to comment.