Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Clean up and extend expression geometry functions:
- New expression functions for area(geom), perimeter(geom),
point_n(geom), start_point(geom), end_point(geom), make_point(x,y)
- Add new variant to length() function which takes a geometry object,
allows for length(geom) evaluation.
- Rename x_at, y_at to $x_at, $y_at (alias old names) to reflect that
these only work on current feature geometry
- Add x(geom), y(geom) functions which return x and y coordinate
for point geometries or centroid x/y for non-point geometries
(fix #11008)
  • Loading branch information
nyalldawson committed Sep 17, 2015
1 parent d82a018 commit 55027e5
Show file tree
Hide file tree
Showing 13 changed files with 291 additions and 39 deletions.
@@ -1,7 +1,7 @@
{
"function": "x_at",
"function": "$x_at",
"description": "Retrieves a x coordinate of the current feature's geometry.",
"arguments": [ {"arg":"i","description":"index of point of a line (indices start at 0; negative values apply from the last index)"}],
"examples": [ { "expression":"x_at(1)", "returns":"5"}
"examples": [ { "expression":"$x_at(1)", "returns":"5"}
]
}
@@ -1,7 +1,7 @@
{
"function": "y_at",
"function": "$y_at",
"description": "Retrieves a y coordinate of the current feature's geometry.",
"arguments": [ {"arg":"i","description":"index of point of a line (indices start at 0; negative values apply from the last index)"}],
"examples": [ { "expression":"y_at(1)", "returns":"2"}
"examples": [ { "expression":"$y_at(1)", "returns":"2"}
]
}
7 changes: 7 additions & 0 deletions resources/function_help/json/area
@@ -0,0 +1,7 @@
{
"function": "area",
"description": "Returns the area of a geometry polygon object. Calculations are in the Spatial Reference System of this geometry.",
"arguments": [ {"arg":"geometry","description":"polygon geometry object"}],
"examples": [ { "expression":"area(geom_from_wkt('POLYGON((0 0, 4 0, 4 2, 0 2, 0 0))'))", "returns":"8.0"}]
}

6 changes: 6 additions & 0 deletions resources/function_help/json/end_point
@@ -0,0 +1,6 @@
{
"function": "end_point",
"description": "Returns the last node from a geometry.",
"arguments": [ {"arg":"geometry","description":"geometry object"} ],
"examples": [ { "expression":"geom_to_wkt(end_point(geom_from_wkt('LINESTRING(4 0, 4 2, 0 2)')))", "returns":"'Point (0 2)'"}]
}
16 changes: 12 additions & 4 deletions resources/function_help/json/length
@@ -1,7 +1,15 @@
{
"function": "length",
"description": "Returns the length of a string.",
"arguments": [ {"arg":"string","description":"string to count length of"}],
"examples": [ { "expression":"length('hello')", "returns":"5"}
]
"description": "Returns the number of characters in a string or the length of a geometry linestring.",
"variants": [
{ "variant": "String variant",
"variant_description": "Returns the number of characters in a string.",
"arguments": [ {"arg":"string","description":"string to count length of"} ],
"examples": [ { "expression":"length('hello')", "returns":"5"} ] },
{
"variant": "Geometry variant",
"variant_description": "Calculate the length of a geometry line object. Calculations are in the Spatial Reference System of this geometry.",
"arguments": [ {"arg":"geometry","description":"line geometry object"}],
"examples": [ { "expression":"length(geom_from_wkt('LINESTRING(0 0, 4 0)'))", "returns":"4.0"}]
}]
}
7 changes: 7 additions & 0 deletions resources/function_help/json/make_point
@@ -0,0 +1,7 @@
{
"function": "make_point",
"description": "Creates a point geometry from an x and y value.",
"arguments": [ {"arg":"x","description":"x coordinate of point"},
{"arg":"y","description":"y coordinate of point"} ],
"examples": [ { "expression":"geom_to_wkt(make_point(2,4))", "returns":"'Point (2 4)'"}]
}
7 changes: 7 additions & 0 deletions resources/function_help/json/perimeter
@@ -0,0 +1,7 @@
{
"function": "perimeter",
"description": "Returns the perimeter of a geometry polygon object. Calculations are in the Spatial Reference System of this geometry.",
"arguments": [ {"arg":"geometry","description":"polygon geometry object"}],
"examples": [ { "expression":"perimeter(geom_from_wkt('POLYGON((0 0, 4 0, 4 2, 0 2, 0 0))'))", "returns":"12.0"}]
}

7 changes: 7 additions & 0 deletions resources/function_help/json/point_n
@@ -0,0 +1,7 @@
{
"function": "point_n",
"description": "Returns a specific node from a geometry.",
"arguments": [ {"arg":"geometry","description":"geometry object"},
{"arg":"index","description":"index of node to return, where 1 is the first node"} ],
"examples": [ { "expression":"geom_to_wkt(point_n(geom_from_wkt('POLYGON((0 0, 4 0, 4 2, 0 2, 0 0))'),2))", "returns":"'Point (4 0)'"}]
}
6 changes: 6 additions & 0 deletions resources/function_help/json/start_point
@@ -0,0 +1,6 @@
{
"function": "start_point",
"description": "Returns the first node from a geometry.",
"arguments": [ {"arg":"geometry","description":"geometry object"} ],
"examples": [ { "expression":"geom_to_wkt(start_point(geom_from_wkt('LINESTRING(4 0, 4 2, 0 2)')))", "returns":"'Point (4 0)'"}]
}
7 changes: 7 additions & 0 deletions resources/function_help/json/x
@@ -0,0 +1,7 @@
{
"function": "x",
"description": "Returns the x coordinate of a point geometry, or the x-coordinate of the centroid for a non-point geometry.",
"arguments": [ {"arg":"geom","description":"a geometry"}],
"examples": [ { "expression":"x( geom_from_wkt( 'POINT(2 5)' ) )", "returns":"2"}
]
}
7 changes: 7 additions & 0 deletions resources/function_help/json/y
@@ -0,0 +1,7 @@
{
"function": "y",
"description": "Returns the y coordinate of a point geometry, or the y-coordinate of the centroid for a non-point geometry.",
"arguments": [ {"arg":"geom","description":"a geometry"}],
"examples": [ { "expression":"y( geom_from_wkt( 'POINT(2 5)' ) )", "returns":"5"}
]
}
191 changes: 170 additions & 21 deletions src/core/qgsexpression.cpp
Expand Up @@ -38,6 +38,8 @@
#include "qgsexpressioncontext.h"
#include "qgsproject.h"
#include "qgsstringutils.h"
#include "qgsgeometrycollectionv2.h"
#include "qgspointv2.h"

// from parser
extern QgsExpression::Node* parseExpression( const QString& str, QString& parserErrorMsg );
Expand Down Expand Up @@ -771,9 +773,22 @@ static QVariant fcnWordwrap( const QVariantList& values, const QgsExpressionCont

static QVariant fcnLength( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent )
{
// two variants, one for geometry, one for string
if ( values.at( 0 ).canConvert<QgsGeometry>() )
{
//geometry variant
QgsGeometry geom = getGeometry( values.at( 0 ), parent );
if ( geom.type() != QGis::Line )
return QVariant();

return QVariant( geom.length() );
}

//otherwise fall back to string variant
QString str = getStringValue( values.at( 0 ), parent );
return QVariant( str.length() );
}

static QVariant fcnReplace( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent )
{
QString str = getStringValue( values.at( 0 ), parent );
Expand Down Expand Up @@ -1171,6 +1186,7 @@ static QVariant fcnX( const QVariantList&, const QgsExpressionContext* context,
return g->asPoint().x();
}
}

static QVariant fcnY( const QVariantList&, const QgsExpressionContext* context, QgsExpression* )
{
FEAT_FROM_CONTEXT( context, f );
Expand All @@ -1185,6 +1201,106 @@ static QVariant fcnY( const QVariantList&, const QgsExpressionContext* context,
}
}

static QVariant fcnGeomX( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent )
{
QgsGeometry geom = getGeometry( values.at( 0 ), parent );
if ( geom.isEmpty() )
return QVariant();

//if single point, return the point's x coordinate
if ( geom.type() == QGis::Point && !geom.isMultipart() )
{
return geom.asPoint().x();
}

//otherwise return centroid x
QgsGeometry* centroid = geom.centroid();
QVariant result( centroid->asPoint().x() );
delete centroid;
return result;
}

static QVariant fcnGeomY( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent )
{
QgsGeometry geom = getGeometry( values.at( 0 ), parent );
if ( geom.isEmpty() )
return QVariant();

//if single point, return the point's y coordinate
if ( geom.type() == QGis::Point && !geom.isMultipart() )
{
return geom.asPoint().y();
}

//otherwise return centroid y
QgsGeometry* centroid = geom.centroid();
QVariant result( centroid->asPoint().y() );
delete centroid;
return result;
}

static QVariant fcnPointN( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent )
{
QgsGeometry geom = getGeometry( values.at( 0 ), parent );

if ( geom.isEmpty() )
return QVariant();

//idx is 1 based
int idx = getIntValue( values.at( 1 ), parent ) - 1;

QgsVertexId vId;
if ( idx < 0 || !geom.vertexIdFromVertexNr( idx, vId ) )
{
parent->setEvalErrorString( QObject::tr( "Point index is out of range" ) );
return QVariant();
}

QgsPointV2 point = geom.geometry()->vertexAt( vId );
return QVariant::fromValue( QgsGeometry( new QgsPointV2( point ) ) );
}

static QVariant fcnStartPoint( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent )
{
QgsGeometry geom = getGeometry( values.at( 0 ), parent );

if ( geom.isEmpty() )
return QVariant();

QgsVertexId vId;
if ( !geom.vertexIdFromVertexNr( 0, vId ) )
{
return QVariant();
}

QgsPointV2 point = geom.geometry()->vertexAt( vId );
return QVariant::fromValue( QgsGeometry( new QgsPointV2( point ) ) );
}

static QVariant fcnEndPoint( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent )
{
QgsGeometry geom = getGeometry( values.at( 0 ), parent );

if ( geom.isEmpty() )
return QVariant();

QgsVertexId vId;
if ( !geom.vertexIdFromVertexNr( geom.geometry()->nCoordinates() - 1, vId ) )
{
return QVariant();
}

QgsPointV2 point = geom.geometry()->vertexAt( vId );
return QVariant::fromValue( QgsGeometry( new QgsPointV2( point ) ) );
}

static QVariant fcnMakePoint( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent )
{
double x = getDoubleValue( values.at( 0 ), parent );
double y = getDoubleValue( values.at( 1 ), parent );
return QVariant::fromValue( QgsGeometry( new QgsPointV2( x, y ) ) );
}

static QVariant pointAt( const QVariantList& values, const QgsExpressionContext* context, QgsExpression* parent ) // helper function
{
FEAT_FROM_CONTEXT( context, f );
Expand Down Expand Up @@ -1251,24 +1367,48 @@ static QVariant fcnGeomArea( const QVariantList&, const QgsExpressionContext* co
QgsDistanceArea* calc = parent->geomCalculator();
return QVariant( calc->measure( f.constGeometry() ) );
}

static QVariant fcnArea( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent )
{
QgsGeometry geom = getGeometry( values.at( 0 ), parent );

if ( geom.type() != QGis::Polygon )
return QVariant();

return QVariant( geom.area() );
}

static QVariant fcnGeomLength( const QVariantList&, const QgsExpressionContext* context, QgsExpression* parent )
{
FEAT_FROM_CONTEXT( context, f );
ENSURE_GEOM_TYPE( f, g, QGis::Line );
QgsDistanceArea* calc = parent->geomCalculator();
return QVariant( calc->measure( f.constGeometry() ) );
}

static QVariant fcnGeomPerimeter( const QVariantList&, const QgsExpressionContext* context, QgsExpression* parent )
{
FEAT_FROM_CONTEXT( context, f );
ENSURE_GEOM_TYPE( f, g, QGis::Polygon );
QgsDistanceArea* calc = parent->geomCalculator();
return QVariant( calc->measurePerimeter( f.constGeometry() ) );
}

static QVariant fcnPerimeter( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent )
{
QgsGeometry geom = getGeometry( values.at( 0 ), parent );

if ( geom.type() != QGis::Polygon )
return QVariant();

//length for polygons = perimeter
return QVariant( geom.length() );
}

static QVariant fcnGeomNumPoints( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent )
{
QgsGeometry geom = getGeometry( values.at( 0 ), parent );
return QVariant( geom.geometry()->nCoordinates() );
return QVariant( geom.isEmpty() ? 0 : geom.geometry()->nCoordinates() );
}

static QVariant fcnBounds( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent )
Expand Down Expand Up @@ -1916,9 +2056,10 @@ const QStringList& QgsExpression::BuiltinFunctions()
<< "color_rgb" << "color_rgba" << "ramp_color"
<< "color_hsl" << "color_hsla" << "color_hsv" << "color_hsva"
<< "color_cymk" << "color_cymka"
<< "xat" << "yat" << "$area"
<< "$length" << "$perimeter" << "$x" << "$y" << "num_points"
<< "x_at" << "xat" << "y_at" << "yat" << "x_min" << "xmin" << "x_max" << "xmax"
<< "xat" << "yat" << "$area" << "area" << "perimeter"
<< "$length" << "$perimeter" << "x" << "y" << "$x" << "$y" << "num_points"
<< "point_n" << "start_point" << "end_point" << "make_point"
<< "$x_at" << "x_at" << "xat" << "$y_at" << "y_at" << "yat" << "x_min" << "xmin" << "x_max" << "xmax"
<< "y_min" << "ymin" << "y_max" << "ymax" << "geom_from_wkt" << "geomFromWKT"
<< "geom_from_gml" << "geomFromGML" << "intersects_bbox" << "bbox"
<< "disjoint" << "intersects" << "touches" << "crosses" << "contains"
Expand Down Expand Up @@ -1975,7 +2116,7 @@ const QList<QgsExpression::Function*>& QgsExpression::Functions()
<< new StaticFunction( "to_time", 1, fcnToTime, "Conversions", QString(), false, QStringList(), false, QStringList() << "totime" )
<< new StaticFunction( "to_interval", 1, fcnToInterval, "Conversions", QString(), false, QStringList(), false, QStringList() << "tointerval" )
<< new StaticFunction( "coalesce", -1, fcnCoalesce, "Conditionals", QString(), false, QStringList(), false, QStringList(), true )
<< new StaticFunction( "if", 3, fcnIf, "Conditionals", "", False, QStringList(), true )
<< new StaticFunction( "if", 3, fcnIf, "Conditionals", QString(), False, QStringList(), true )
<< new StaticFunction( "regexp_match", 2, fcnRegexpMatch, "Conditionals" )
<< new StaticFunction( "now", 0, fcnNow, "Date and Time", QString(), false, QStringList(), false, QStringList() << "$now" )
<< new StaticFunction( "age", 2, fcnAge, "Date and Time" )
Expand Down Expand Up @@ -2018,18 +2159,26 @@ const QList<QgsExpression::Function*>& QgsExpression::Functions()
<< new StaticFunction( "color_hsva", 4, fncColorHsva, "Color" )
<< new StaticFunction( "color_cmyk", 4, fcnColorCmyk, "Color" )
<< new StaticFunction( "color_cmyka", 5, fncColorCmyka, "Color" )
<< new StaticFunction( "$geometry", 0, fcnGeometry, "GeometryGroup", "", true )
<< new StaticFunction( "$area", 0, fcnGeomArea, "GeometryGroup", "", true )
<< new StaticFunction( "$length", 0, fcnGeomLength, "GeometryGroup", "", true )
<< new StaticFunction( "$perimeter", 0, fcnGeomPerimeter, "GeometryGroup", "", true )
<< new StaticFunction( "$x", 0, fcnX, "GeometryGroup", "", true )
<< new StaticFunction( "$y", 0, fcnY, "GeometryGroup", "", true )
<< new StaticFunction( "x_at", 1, fcnXat, "GeometryGroup", "", true, QStringList(), false, QStringList() << "xat" )
<< new StaticFunction( "y_at", 1, fcnYat, "GeometryGroup", "", true, QStringList(), false, QStringList() << "yat" )
<< new StaticFunction( "x_min", 1, fcnXMin, "GeometryGroup", "", true, QStringList(), false, QStringList() << "xmin" )
<< new StaticFunction( "x_max", 1, fcnXMax, "GeometryGroup", "", true, QStringList(), false, QStringList() << "xmax" )
<< new StaticFunction( "y_min", 1, fcnYMin, "GeometryGroup", "", true, QStringList(), false, QStringList() << "ymin" )
<< new StaticFunction( "y_max", 1, fcnYMax, "GeometryGroup", "", true, QStringList(), false, QStringList() << "ymax" )
<< new StaticFunction( "$geometry", 0, fcnGeometry, "GeometryGroup", QString(), true )
<< new StaticFunction( "$area", 0, fcnGeomArea, "GeometryGroup", QString(), true )
<< new StaticFunction( "area", 1, fcnArea, "GeometryGroup" )
<< new StaticFunction( "$length", 0, fcnGeomLength, "GeometryGroup", QString(), true )
<< new StaticFunction( "$perimeter", 0, fcnGeomPerimeter, "GeometryGroup", QString(), true )
<< new StaticFunction( "perimeter", 1, fcnPerimeter, "GeometryGroup" )
<< new StaticFunction( "$x", 0, fcnX, "GeometryGroup", QString(), true )
<< new StaticFunction( "$y", 0, fcnY, "GeometryGroup", QString(), true )
<< new StaticFunction( "x", 1, fcnGeomX, "GeometryGroup" )
<< new StaticFunction( "y", 1, fcnGeomY, "GeometryGroup" )
<< new StaticFunction( "point_n", 2, fcnPointN, "GeometryGroup" )
<< new StaticFunction( "start_point", 1, fcnStartPoint, "GeometryGroup" )
<< new StaticFunction( "end_point", 1, fcnEndPoint, "GeometryGroup" )
<< new StaticFunction( "make_point", 2, fcnMakePoint, "GeometryGroup" )
<< new StaticFunction( "$x_at", 1, fcnXat, "GeometryGroup", QString(), true, QStringList(), false, QStringList() << "xat" << "x_at" )
<< new StaticFunction( "$y_at", 1, fcnYat, "GeometryGroup", QString(), true, QStringList(), false, QStringList() << "yat" << "y_at" )
<< new StaticFunction( "x_min", 1, fcnXMin, "GeometryGroup", QString(), false, QStringList(), false, QStringList() << "xmin" )
<< new StaticFunction( "x_max", 1, fcnXMax, "GeometryGroup", QString(), false, QStringList(), false, QStringList() << "xmax" )
<< new StaticFunction( "y_min", 1, fcnYMin, "GeometryGroup", QString(), false, QStringList(), false, QStringList() << "ymin" )
<< new StaticFunction( "y_max", 1, fcnYMax, "GeometryGroup", QString(), false, QStringList(), false, QStringList() << "ymax" )
<< new StaticFunction( "geom_from_wkt", 1, fcnGeomFromWKT, "GeometryGroup", QString(), false, QStringList(), false, QStringList() << "geomFromWKT" )
<< new StaticFunction( "geom_from_gml", 1, fcnGeomFromGML, "GeometryGroup", QString(), false, QStringList(), false, QStringList() << "geomFromGML" )
<< new StaticFunction( "intersects_bbox", 2, fcnBbox, "GeometryGroup", QString(), false, QStringList(), false, QStringList() << "bbox" )
Expand All @@ -2042,10 +2191,10 @@ const QList<QgsExpression::Function*>& QgsExpression::Functions()
<< new StaticFunction( "within", 2, fcnWithin, "GeometryGroup" )
<< new StaticFunction( "buffer", -1, fcnBuffer, "GeometryGroup" )
<< new StaticFunction( "centroid", 1, fcnCentroid, "GeometryGroup" )
<< new StaticFunction( "bounds", 1, fcnBounds, "GeometryGroup", "", true )
<< new StaticFunction( "num_points", 1, fcnGeomNumPoints, "GeometryGroup", "", true )
<< new StaticFunction( "bounds_width", 1, fcnBoundsWidth, "GeometryGroup", "", true )
<< new StaticFunction( "bounds_height", 1, fcnBoundsHeight, "GeometryGroup", "", true )
<< new StaticFunction( "bounds", 1, fcnBounds, "GeometryGroup" )
<< new StaticFunction( "num_points", 1, fcnGeomNumPoints, "GeometryGroup" )
<< new StaticFunction( "bounds_width", 1, fcnBoundsWidth, "GeometryGroup" )
<< new StaticFunction( "bounds_height", 1, fcnBoundsHeight, "GeometryGroup" )
<< new StaticFunction( "convex_hull", 1, fcnConvexHull, "GeometryGroup", QString(), false, QStringList(), false, QStringList() << "convexHull" )
<< new StaticFunction( "difference", 2, fcnDifference, "GeometryGroup" )
<< new StaticFunction( "distance", 2, fcnDistance, "GeometryGroup" )
Expand Down

2 comments on commit 55027e5

@NathanW2
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How hard do you think a transform function would be? so we can do length(transform(geom, code, code))

@NathanW2
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Never mind I see we already have it. Didn't look hard enough.

Please sign in to comment.