Skip to content

Commit 7b9cb74

Browse files
author
jef
committed
[FEATURE] add field calculator functions (implements #3177)
- add support for functions with 2 or three arguments - add function atan2(y,x) - add length(string) to determine string length - add replace(string,from,to) to do string replaces - add substr(string,from,length) to retrieve substrings - add (preliminary) online help to field calculator with a listing of operations. git-svn-id: http://svn.osgeo.org/qgis/trunk@14533 c8812cc2-4d05-0410-92ff-de0c093fc19c
1 parent 9142dc6 commit 7b9cb74

8 files changed

+170
-36
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<h3>Field Calculator</h3>
2+
The field calculator allows you to update fields with expressions.
3+
4+
<h4>Supported Operations</h4>
5+
6+
<table border=1>
7+
<tr>
8+
<th>Operation</th>
9+
<th>Description</th>
10+
</tr>
11+
<tr>
12+
<td>
13+
<tt>column_name</tt><br>
14+
<tt>"column_name"</tt>
15+
</td>
16+
<td>value of field <tt>column_name</tt></td>
17+
</tr>
18+
<tr><td>'string'</td><td>literal string value</td></tr>
19+
<tr><td><i>number</i></td><td>number</td></tr>
20+
<tr><td>NULL</td><td>null value</td></tr>
21+
<tr><td><tt>a</tt> OR <tt>b</tt></td><td><tt>a</tt> or <tt>b</tt> are true.</td></tr>
22+
<tr><td><tt>a</tt> AND <tt>b</tt></td><td><tt>a</tt> and <tt>b</tt> are true.</td></tr>
23+
<tr><td>NOT <tt>a</tt></td><td>inverted boolean value of <tt>a</tt></td></tr>
24+
<tr><td><tt>a</tt> IS NULL</td><td><tt>a</tt> has no value</td></tr>
25+
<tr><td><tt>a</tt> IS NOT NULL</td><td><tt>a</tt> has <tt>a</tt> value</td></tr>
26+
<tr><td><tt>a</tt> IN ( value, [, value] )</td><td><tt>a</tt> is one of the listed values</td></tr>
27+
<tr><td><tt>a</tt> NOT IN ( value, [, value] )</td><td><tt>a</tt> is not one of the listed values</td></tr>
28+
<tr><td><tt>a</tt> = <tt>b</tt></td><td><tt>a</tt> and <tt>b</tt> are equal</td><tr>
29+
<tr>
30+
<td>
31+
<tt>a</tt> != <tt>b</tt><br>
32+
<tt>a</tt> &lt;&gt; <tt>b</tt>
33+
</td>
34+
<td><tt>a</tt> and <tt>b</tt> are not equal</td>
35+
</tr>
36+
<tr><td><tt>a</tt> &lt;= <tt>b</tt></td><td><tt>a</tt> is less or equal <tt>b</tt></td></tr>
37+
<tr><td><tt>a</tt> &gt;= <tt>b</tt></td><td><tt>a</tt> is greater or equal <tt>b</tt></td></tr>
38+
<tr><td><tt>a</tt> &gt; <tt>b</tt></td><td><tt>a</tt> is greater than <tt>b</tt></td></tr>
39+
<tr><td><tt>a</tt> &lt; <tt>b</tt></td><td><tt>a</tt> is less than <tt>b</tt></td></tr>
40+
<tr><td><tt>a</tt> ~ <tt>b</tt></td></td><td><tt>a</tt> matches regular expression <tt>b</tt></td></tr>
41+
<tr><td><tt>a</tt> LIKE <tt>b</tt></td><td><tt>a</tt> is like <tt>b</tt></td></tr>
42+
<tr><td><tt>a</tt> ILIKE <tt>b</tt></td><td><tt>a</tt> is like <tt>b</tt> (case insensitive)</td></tr>
43+
<tr><td>sqrt(<tt>a</tt>)</td><td>square root</td></tr>
44+
<tr><td>sin(<tt>a</tt>)</td><td>sinus of <tt>a</tt></td></tr>
45+
<tr><td>cos(<tt>a</tt>)</td><td>cosinus of <tt>b</tt></td></tr>
46+
<tr><td>tan(<tt>a</tt>)</td><td>tangens of <tt>a</tt></td></tr>
47+
<tr><td>asin(<tt>a</tt>)</td><td>arcussinus of <tt>a</tt></td></tr>
48+
<tr><td>acos(<tt>a</tt>)</td><td>arcuscosinus of <tt>a</tt></td></tr>
49+
<tr><td>atan(<tt>a</tt>)</td><td>arcustangens of <tt>a</tt></td></tr>
50+
<tr><td>to int(<tt>a</tt>)</td><td>convert string <tt>a</tt> to integer</td></tr>
51+
<tr><td>to real(<tt>a</tt>)</td><td>convert string <tt>a</tt> to real</td></tr>
52+
<tr><td>to string(<tt>a</tt>)</td><td>convert number <tt>a</tt> to string</td></tr>
53+
<tr><td>lower(<tt>a</tt>)</td><td>convert string <tt>a</tt> to lower case</td></tr>
54+
<tr><td>upper(<tt>a</tt>)</td><td>convert string <tt>a</tt> to upper case</td></tr>
55+
<tr><td>length(<tt>a</tt>)</td><td>length of string <tt>a</tt></td></tr>
56+
<tr><td>atan2(y,x)</td><td>arcustangens of y/x using the signs of the two arguments to determine the quadrant of the result.</td></tr>
57+
<tr><td>replace(<tt>a</tt>,replacethis,withthat)</td><td>replace replacethis with withthat in string <tt>a</tt></td></td>
58+
<tr><td>substr(<tt>a</tt>,from,len)</td><td>len characters of string <tt>a</tt> starting from from (first character index is 1)</td></td>
59+
<tr><td><tt>a</tt> || <tt>b</tt></td><td>concatenate strings <tt>a</tt> and <tt>b</tt></td></tr>
60+
<tr><td>$rownum</td><td>number current row</td></tr>
61+
<tr><td>$area</td><td>area of polygon</td></tr>
62+
<tr><td>$length</td><td>area of line</td></tr>
63+
<tr><td>$id</td><td>feature id</td></tr>
64+
<tr><td><tt>a</tt> ^ <tt>b</tt></td><td><tt>a</tt> raised to the power of <tt>b</tt></td></tr>
65+
<tr><td><tt>a</tt> * <tt>b</tt></td><td><tt>a</tt> multiplied by <tt>b</tt></td></tr>
66+
<tr><td><tt>a</tt> * <tt>b</tt></td><td><tt>a</tt> divided by <tt>b</tt></td></tr>
67+
<tr><td><tt>a</tt> + <tt>b</tt></td><td><tt>a</tt> plus <tt>b</tt></td></tr>
68+
<tr><td><tt>a</tt> - <tt>b</tt></td><td><tt>a</tt> minus <tt>b</tt></td></tr>
69+
<tr><td>+<tt>a</tt></td><td>positive sign</td></tr>
70+
<tr><td>-<tt>a</tt></td><td>negative value of <tt>a</tt></td></tr>
71+
</table>

src/app/qgsfieldcalculator.cpp

-2
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,6 @@ QgsFieldCalculator::QgsFieldCalculator( QgsVectorLayer* vl ): QDialog(), mVector
3636
mOuputFieldWidthSpinBox->setValue( 10 );
3737
mOutputFieldPrecisionSpinBox->setValue( 3 );
3838

39-
40-
4139
//disable ok button until there is text for output field and expression
4240
mButtonBox->button( QDialogButtonBox::Ok )->setEnabled( false );
4341

src/app/qgsfieldcalculator.h

+3
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#define QGSFIELDCALCULATOR_H
1818

1919
#include "ui_qgsfieldcalculatorbase.h"
20+
#include "qgscontexthelp.h"
2021

2122
class QgsVectorLayer;
2223

@@ -61,6 +62,8 @@ class QgsFieldCalculator: public QDialog, private Ui::QgsFieldCalculatorBase
6162
void on_mExpressionTextEdit_textChanged();
6263
void on_mOutputFieldTypeComboBox_activated( int index );
6364

65+
void on_mButtonBox_helpRequested() { QgsContextHelp::run( metaObject()->className() ); }
66+
6467
private:
6568
//default constructor forbidden
6669
QgsFieldCalculator();

src/core/qgssearchstringlexer.ll

+18-12
Original file line numberDiff line numberDiff line change
@@ -83,18 +83,24 @@ string "'"{str_char}*"'"
8383
"LIKE" { yylval.op = QgsSearchTreeNode::opLike; return COMPARISON; }
8484
"ILIKE" { yylval.op = QgsSearchTreeNode::opILike; return COMPARISON; }
8585
86-
"sqrt" { yylval.op = QgsSearchTreeNode::opSQRT; return FUNCTION;}
87-
"sin" { yylval.op = QgsSearchTreeNode::opSIN; return FUNCTION;}
88-
"cos" { yylval.op = QgsSearchTreeNode::opCOS; return FUNCTION;}
89-
"tan" { yylval.op = QgsSearchTreeNode::opTAN; return FUNCTION;}
90-
"asin" { yylval.op = QgsSearchTreeNode::opASIN; return FUNCTION;}
91-
"acos" { yylval.op = QgsSearchTreeNode::opACOS; return FUNCTION;}
92-
"atan" { yylval.op = QgsSearchTreeNode::opATAN; return FUNCTION;}
93-
"to int" { yylval.op = QgsSearchTreeNode::opTOINT; return FUNCTION;}
94-
"to real" { yylval.op = QgsSearchTreeNode::opTOREAL; return FUNCTION;}
95-
"to string" { yylval.op = QgsSearchTreeNode::opTOSTRING; return FUNCTION;}
96-
"lower" { yylval.op = QgsSearchTreeNode::opLOWER; return FUNCTION;}
97-
"upper" { yylval.op = QgsSearchTreeNode::opUPPER; return FUNCTION;}
86+
"sqrt" { yylval.op = QgsSearchTreeNode::opSQRT; return FUNCTION1;}
87+
"sin" { yylval.op = QgsSearchTreeNode::opSIN; return FUNCTION1;}
88+
"cos" { yylval.op = QgsSearchTreeNode::opCOS; return FUNCTION1;}
89+
"tan" { yylval.op = QgsSearchTreeNode::opTAN; return FUNCTION1;}
90+
"asin" { yylval.op = QgsSearchTreeNode::opASIN; return FUNCTION1;}
91+
"acos" { yylval.op = QgsSearchTreeNode::opACOS; return FUNCTION1;}
92+
"atan" { yylval.op = QgsSearchTreeNode::opATAN; return FUNCTION1;}
93+
"to int" { yylval.op = QgsSearchTreeNode::opTOINT; return FUNCTION1;}
94+
"to real" { yylval.op = QgsSearchTreeNode::opTOREAL; return FUNCTION1;}
95+
"to string" { yylval.op = QgsSearchTreeNode::opTOSTRING; return FUNCTION1;}
96+
"lower" { yylval.op = QgsSearchTreeNode::opLOWER; return FUNCTION1;}
97+
"upper" { yylval.op = QgsSearchTreeNode::opUPPER; return FUNCTION1;}
98+
"length" { yylval.op = QgsSearchTreeNode::opSTRLEN; return FUNCTION1;}
99+
100+
"atan2" { yylval.op = QgsSearchTreeNode::opATAN2; return FUNCTION2;}
101+
102+
"replace" { yylval.op = QgsSearchTreeNode::opREPLACE; return FUNCTION3;}
103+
"substr" { yylval.op = QgsSearchTreeNode::opSUBSTR; return FUNCTION3;}
98104
99105
"||" { return CONCAT; }
100106

src/core/qgssearchstringparser.yy

+22-3
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,9 @@ void addToTmpNodes(QgsSearchTreeNode* node);
6161

6262
%token <number> NUMBER
6363
%token <op> COMPARISON
64-
%token <op> FUNCTION
64+
%token <op> FUNCTION1
65+
%token <op> FUNCTION2
66+
%token <op> FUNCTION3
6567
%token CONCAT
6668
%token IS
6769
%token IN
@@ -135,11 +137,28 @@ comp_predicate:
135137

136138
scalar_exp_list:
137139
scalar_exp_list ',' scalar_exp { $$ = $1; $1->append($3); joinTmpNodes($1,$1,$3); }
138-
| scalar_exp { $$ = new QgsSearchTreeNode( QgsSearchTreeNode::tNodeList ); $$->append($1); joinTmpNodes($$,$1,0); }
140+
| scalar_exp
141+
{
142+
$$ = new QgsSearchTreeNode( QgsSearchTreeNode::tNodeList );
143+
$$->append($1);
144+
joinTmpNodes($$,$1,0);
145+
}
139146
;
140147

141148
scalar_exp:
142-
FUNCTION '(' scalar_exp ')' { $$ = new QgsSearchTreeNode($1, $3, 0); joinTmpNodes($$, $3, 0);}
149+
FUNCTION1 '(' scalar_exp ')' { $$ = new QgsSearchTreeNode($1, $3, 0); joinTmpNodes($$, $3, 0); }
150+
| FUNCTION2 '(' scalar_exp ',' scalar_exp ')' { $$ = new QgsSearchTreeNode($1, $3, $5); joinTmpNodes($$, $3, $5); }
151+
| FUNCTION3 '(' scalar_exp ',' scalar_exp ',' scalar_exp ')'
152+
{
153+
QgsSearchTreeNode *args = new QgsSearchTreeNode( QgsSearchTreeNode::tNodeList );
154+
args->append($3);
155+
args->append($5);
156+
args->append($7);
157+
158+
$$ = new QgsSearchTreeNode($1, args, 0);
159+
joinTmpNodes($$, $3, $5);
160+
joinTmpNodes($$, $$, $7);
161+
}
143162
| scalar_exp '^' scalar_exp { $$ = new QgsSearchTreeNode(QgsSearchTreeNode::opPOW, $1, $3); joinTmpNodes($$,$1,$3); }
144163
| scalar_exp '*' scalar_exp { $$ = new QgsSearchTreeNode(QgsSearchTreeNode::opMUL, $1, $3); joinTmpNodes($$,$1,$3); }
145164
| scalar_exp '/' scalar_exp { $$ = new QgsSearchTreeNode(QgsSearchTreeNode::opDIV, $1, $3); joinTmpNodes($$,$1,$3); }

src/core/qgssearchtreenode.cpp

+51-18
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,10 @@ QgsSearchTreeNode::QgsSearchTreeNode( const QgsSearchTreeNode& node )
115115
else
116116
mRight = NULL;
117117

118-
foreach( QgsSearchTreeNode *lnode, node.mNodeList )
119-
mNodeList.append( new QgsSearchTreeNode( *lnode ) );
118+
foreach( QgsSearchTreeNode * lnode, node.mNodeList )
119+
{
120+
mNodeList.append( new QgsSearchTreeNode( *lnode ) );
121+
}
120122

121123
init();
122124
}
@@ -209,7 +211,8 @@ QString QgsSearchTreeNode::makeSearchString()
209211
if ( mOp == opSQRT || mOp == opSIN || mOp == opCOS || mOp == opTAN ||
210212
mOp == opASIN || mOp == opACOS || mOp == opATAN ||
211213
mOp == opTOINT || mOp == opTOREAL || mOp == opTOSTRING ||
212-
mOp == opLOWER || mOp == opUPPER )
214+
mOp == opLOWER || mOp == opUPPER || mOp == opSTRLEN ||
215+
mOp == opATAN2 || mOp == opREPLACE || mOp == opSUBSTR )
213216
{
214217
// functions
215218
switch ( mOp )
@@ -226,6 +229,10 @@ QString QgsSearchTreeNode::makeSearchString()
226229
case opTOSTRING: str += "to string"; break;
227230
case opLOWER: str += "lower"; break;
228231
case opUPPER: str += "upper"; break;
232+
case opATAN2: str += "atan2"; break;
233+
case opSTRLEN: str += "length"; break;
234+
case opREPLACE: str += "replace"; break;
235+
case opSUBSTR: str += "substr"; break;
229236
default: str += "?";
230237
}
231238
// currently all functions take one parameter
@@ -306,7 +313,7 @@ QString QgsSearchTreeNode::makeSearchString()
306313
else if ( mType == tNodeList )
307314
{
308315
QStringList items;
309-
foreach( QgsSearchTreeNode *node, mNodeList )
316+
foreach( QgsSearchTreeNode * node, mNodeList )
310317
{
311318
items << node->makeSearchString();
312319
}
@@ -461,7 +468,7 @@ bool QgsSearchTreeNode::checkAgainst( const QgsFieldMap& fields, QgsFeature &f )
461468
return false;
462469
}
463470

464-
foreach( QgsSearchTreeNode *node, mRight->mNodeList )
471+
foreach( QgsSearchTreeNode * node, mRight->mNodeList )
465472
{
466473
if ( !getValue( value2, node, fields, f ) )
467474
{
@@ -480,7 +487,6 @@ bool QgsSearchTreeNode::checkAgainst( const QgsFieldMap& fields, QgsFeature &f )
480487

481488
return mOp == opNOTIN;
482489
}
483-
break;
484490

485491
case opRegexp:
486492
case opLike:
@@ -545,7 +551,7 @@ bool QgsSearchTreeNode::getValue( QgsSearchTreeValue& value,
545551
value = node->valueAgainst( fields, f );
546552
if ( value.isError() )
547553
{
548-
switch (( int )value.number() )
554+
switch (( int ) value.number() )
549555
{
550556
case 1:
551557
mError = QObject::tr( "Referenced column wasn't found: %1" ).arg( value.string() );
@@ -587,7 +593,6 @@ QgsSearchTreeValue QgsSearchTreeNode::valueAgainst( const QgsFieldMap& fields, Q
587593

588594
switch ( mType )
589595
{
590-
591596
case tNumber:
592597
QgsDebugMsgLevel( "number: " + QString::number( mNumber ), 2 );
593598
return QgsSearchTreeValue( mNumber );
@@ -637,13 +642,23 @@ QgsSearchTreeValue QgsSearchTreeNode::valueAgainst( const QgsFieldMap& fields, Q
637642
// arithmetic operators
638643
case tOperator:
639644
{
640-
QgsSearchTreeValue value1, value2;
645+
QgsSearchTreeValue value1, value2, value3;
641646
if ( mLeft )
642647
{
643-
if ( !getValue( value1, mLeft, fields, f ) ) return value1;
648+
if ( mLeft->type() != tNodeList )
649+
{
650+
if ( !getValue( value1, mLeft, fields, f ) ) return value1;
651+
}
652+
else
653+
{
654+
if ( mLeft->mNodeList.size() > 0 && !getValue( value1, mLeft->mNodeList[0], fields, f ) ) return value1;
655+
if ( mLeft->mNodeList.size() > 1 && !getValue( value2, mLeft->mNodeList[1], fields, f ) ) return value2;
656+
if ( mLeft->mNodeList.size() > 2 && !getValue( value3, mLeft->mNodeList[2], fields, f ) ) return value3;
657+
}
644658
}
645659
if ( mRight )
646660
{
661+
Q_ASSERT( !mLeft || mLeft->type() != tNodeList );
647662
if ( !getValue( value2, mRight, fields, f ) ) return value2;
648663
}
649664

@@ -716,6 +731,23 @@ QgsSearchTreeValue QgsSearchTreeNode::valueAgainst( const QgsFieldMap& fields, Q
716731
}
717732
}
718733

734+
// string operations
735+
switch ( mOp )
736+
{
737+
case opLOWER:
738+
return QgsSearchTreeValue( value1.string().toLower() );
739+
case opUPPER:
740+
return QgsSearchTreeValue( value1.string().toUpper() );
741+
case opSTRLEN:
742+
return QgsSearchTreeValue( value1.string().length() );
743+
case opREPLACE:
744+
return QgsSearchTreeValue( value1.string().replace( value2.string(), value3.string() ) );
745+
case opSUBSTR:
746+
return QgsSearchTreeValue( value1.string().mid( value2.number() - 1, value3.number() ) );
747+
default:
748+
break;
749+
}
750+
719751
// for other operators, convert strings to numbers if needed
720752
double val1, val2;
721753
if ( value1.isNumeric() )
@@ -740,8 +772,6 @@ QgsSearchTreeValue QgsSearchTreeNode::valueAgainst( const QgsFieldMap& fields, Q
740772
return QgsSearchTreeValue( 2, "" ); // division by zero
741773
else
742774
return QgsSearchTreeValue( val1 / val2 );
743-
default:
744-
return QgsSearchTreeValue( 3, QString::number( mOp ) ); // unknown operator
745775
case opPOW:
746776
if (( val1 == 0 && val2 < 0 ) || ( val2 < 0 && ( val2 - floor( val2 ) ) > 0 ) )
747777
{
@@ -762,16 +792,17 @@ QgsSearchTreeValue QgsSearchTreeNode::valueAgainst( const QgsFieldMap& fields, Q
762792
return QgsSearchTreeValue( acos( val1 ) );
763793
case opATAN:
764794
return QgsSearchTreeValue( atan( val1 ) );
795+
case opATAN2:
796+
return QgsSearchTreeValue( atan2( val1, val2 ) );
765797
case opTOINT:
766798
return QgsSearchTreeValue( int( val1 ) );
767799
case opTOREAL:
768800
return QgsSearchTreeValue( val1 );
769801
case opTOSTRING:
770802
return QgsSearchTreeValue( QString::number( val1 ) );
771-
case opLOWER:
772-
return QgsSearchTreeValue( value1.string().toLower() );
773-
case opUPPER:
774-
return QgsSearchTreeValue( value1.string().toUpper() );
803+
804+
default:
805+
return QgsSearchTreeValue( 3, QString::number( mOp ) ); // unknown operator
775806
}
776807
}
777808

@@ -806,8 +837,10 @@ void QgsSearchTreeNode::append( QgsSearchTreeNode *node )
806837

807838
void QgsSearchTreeNode::append( QList<QgsSearchTreeNode *> nodes )
808839
{
809-
foreach( QgsSearchTreeNode *node, nodes )
810-
mNodeList.append( node );
840+
foreach( QgsSearchTreeNode * node, nodes )
841+
{
842+
mNodeList.append( node );
843+
}
811844
}
812845

813846
int QgsSearchTreeValue::compare( QgsSearchTreeValue& value1, QgsSearchTreeValue& value2, Qt::CaseSensitivity cs )

src/core/qgssearchtreenode.h

+4
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ class CORE_EXPORT QgsSearchTreeNode
7474
opASIN,
7575
opACOS,
7676
opATAN,
77+
opATAN2,
7778

7879
// conversion
7980
opTOINT,
@@ -106,6 +107,9 @@ class CORE_EXPORT QgsSearchTreeNode
106107
opCONCAT,
107108
opLOWER,
108109
opUPPER,
110+
opREPLACE,
111+
opSTRLEN,
112+
opSUBSTR,
109113

110114
opROWNUM
111115
};

src/ui/qgsfieldcalculatorbase.ui

+1-1
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,7 @@
305305
<enum>Qt::Horizontal</enum>
306306
</property>
307307
<property name="standardButtons">
308-
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
308+
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Help|QDialogButtonBox::Ok</set>
309309
</property>
310310
</widget>
311311
</item>

0 commit comments

Comments
 (0)