From fed75afa9535f65fd032a231aa72a21ef733e84c Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 1 Nov 2019 09:14:27 +1000 Subject: [PATCH] [FEATURE] Add expression functions for converting to/from wkb Adds geom_from_wkb and geom_to_wkb, which mirror the existing geom_from_wkt/geom_to_wkt functions but for WKB representations of geometries. Since QGIS 3.6 we've had good support for binary blob values in expressions and field values, so adding these functions allows users to work with binary blob fields containing WKB representations of geometries (e.g. with a geometry generator showing the encoded geometries) --- resources/function_help/json/geom_from_wkb | 7 ++++++ resources/function_help/json/geom_to_wkb | 8 +++++++ src/core/expression/qgsexpressionfunction.cpp | 22 +++++++++++++++++++ src/core/expression/qgsexpressionutils.h | 17 ++++++++++++++ tests/src/core/testqgsexpression.cpp | 4 ++++ 5 files changed, 58 insertions(+) create mode 100644 resources/function_help/json/geom_from_wkb create mode 100644 resources/function_help/json/geom_to_wkb diff --git a/resources/function_help/json/geom_from_wkb b/resources/function_help/json/geom_from_wkb new file mode 100644 index 00000000000..4b19ff39b56 --- /dev/null +++ b/resources/function_help/json/geom_from_wkb @@ -0,0 +1,7 @@ +{ + "name": "geom_from_wkb", + "type": "function", + "description": "Returns a geometry created from a Well-Known Binary (WKB) representation.", + "arguments": [ {"arg":"binary","description":"Well-Known Binary (WKB) representation of a geometry (as a binary blob)"}], + "examples": [ { "expression":"geom_from_wkb( geom_to_wkb( make_point(4,5) ) )", "returns":"a point geometry object"}] +} diff --git a/resources/function_help/json/geom_to_wkb b/resources/function_help/json/geom_to_wkb new file mode 100644 index 00000000000..6d68d87bdec --- /dev/null +++ b/resources/function_help/json/geom_to_wkb @@ -0,0 +1,8 @@ +{ + "name": "geom_to_wkb", + "type": "function", + "description": "Returns the Well-Known Binary (WKB) representation of a geometry as a binary blob.", + "arguments": [ {"arg":"geometry","description":"a geometry"}], + "examples": [ { "expression":"geom_to_wkb( $geometry )", "returns":"binary blob containing a geometry object"} + ] +} diff --git a/src/core/expression/qgsexpressionfunction.cpp b/src/core/expression/qgsexpressionfunction.cpp index f9f4f905bf7..1920b026bde 100644 --- a/src/core/expression/qgsexpressionfunction.cpp +++ b/src/core/expression/qgsexpressionfunction.cpp @@ -2812,6 +2812,7 @@ static QVariant fcnGeometry( const QVariantList &, const QgsExpressionContext *c else return QVariant( QVariant::UserType ); } + static QVariant fcnGeomFromWKT( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * ) { QString wkt = QgsExpressionUtils::getStringValue( values.at( 0 ), parent ); @@ -2819,6 +2820,18 @@ static QVariant fcnGeomFromWKT( const QVariantList &values, const QgsExpressionC QVariant result = !geom.isNull() ? QVariant::fromValue( geom ) : QVariant(); return result; } + +static QVariant fcnGeomFromWKB( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * ) +{ + const QByteArray wkb = QgsExpressionUtils::getBinaryValue( values.at( 0 ), parent ); + if ( wkb.isNull() ) + return QVariant(); + + QgsGeometry geom; + geom.fromWkb( wkb ); + return !geom.isNull() ? QVariant::fromValue( geom ) : QVariant(); +} + static QVariant fcnGeomFromGML( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * ) { QString gml = QgsExpressionUtils::getStringValue( values.at( 0 ), parent ); @@ -3448,6 +3461,7 @@ static QVariant fcnCombine( const QVariantList &values, const QgsExpressionConte QVariant result = !geom.isNull() ? QVariant::fromValue( geom ) : QVariant(); return result; } + static QVariant fcnGeomToWKT( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * ) { if ( values.length() < 1 || values.length() > 2 ) @@ -3461,6 +3475,12 @@ static QVariant fcnGeomToWKT( const QVariantList &values, const QgsExpressionCon return QVariant( wkt ); } +static QVariant fcnGeomToWKB( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * ) +{ + QgsGeometry fGeom = QgsExpressionUtils::getGeometry( values.at( 0 ), parent ); + return fGeom.isNull() ? QVariant() : QVariant( fGeom.asWkb() ); +} + static QVariant fcnAzimuth( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * ) { if ( values.length() != 2 ) @@ -5486,6 +5506,7 @@ const QList &QgsExpression::Functions() << new QgsStaticExpressionFunction( QStringLiteral( "y_min" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "geom" ) ), fcnYMin, QStringLiteral( "GeometryGroup" ), QString(), false, QSet(), false, QStringList() << QStringLiteral( "ymin" ) ) << new QgsStaticExpressionFunction( QStringLiteral( "y_max" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "geom" ) ), fcnYMax, QStringLiteral( "GeometryGroup" ), QString(), false, QSet(), false, QStringList() << QStringLiteral( "ymax" ) ) << new QgsStaticExpressionFunction( QStringLiteral( "geom_from_wkt" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "text" ) ), fcnGeomFromWKT, QStringLiteral( "GeometryGroup" ), QString(), false, QSet(), false, QStringList() << QStringLiteral( "geomFromWKT" ) ) + << new QgsStaticExpressionFunction( QStringLiteral( "geom_from_wkb" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "binary" ) ), fcnGeomFromWKB, QStringLiteral( "GeometryGroup" ), QString(), false, QSet(), false ) << new QgsStaticExpressionFunction( QStringLiteral( "geom_from_gml" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "gml" ) ), fcnGeomFromGML, QStringLiteral( "GeometryGroup" ), QString(), false, QSet(), false, QStringList() << QStringLiteral( "geomFromGML" ) ) << new QgsStaticExpressionFunction( QStringLiteral( "flip_coordinates" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "geom" ) ), fcnFlipCoordinates, QStringLiteral( "GeometryGroup" ) ) << new QgsStaticExpressionFunction( QStringLiteral( "relate" ), -1, fcnRelate, QStringLiteral( "GeometryGroup" ) ) @@ -5605,6 +5626,7 @@ const QList &QgsExpression::Functions() << QgsExpressionFunction::Parameter( QStringLiteral( "geometry2" ) ), fcnCombine, QStringLiteral( "GeometryGroup" ) ) << new QgsStaticExpressionFunction( QStringLiteral( "geom_to_wkt" ), -1, fcnGeomToWKT, QStringLiteral( "GeometryGroup" ), QString(), false, QSet(), false, QStringList() << QStringLiteral( "geomToWKT" ) ) + << new QgsStaticExpressionFunction( QStringLiteral( "geom_to_wkb" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "geometry" ) ), fcnGeomToWKB, QStringLiteral( "GeometryGroup" ), QString(), false, QSet(), false ) << new QgsStaticExpressionFunction( QStringLiteral( "geometry" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "feature" ) ), fcnGetGeometry, QStringLiteral( "GeometryGroup" ), QString(), true ) << new QgsStaticExpressionFunction( QStringLiteral( "transform" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "geom" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "source_auth_id" ) ) diff --git a/src/core/expression/qgsexpressionutils.h b/src/core/expression/qgsexpressionutils.h index 0f0f15fbac9..ad6c1630ed6 100644 --- a/src/core/expression/qgsexpressionutils.h +++ b/src/core/expression/qgsexpressionutils.h @@ -188,6 +188,23 @@ class QgsExpressionUtils return value.toString(); } + /** + * Returns an expression value converted to binary (byte array) value. + * + * An empty byte array will be returned if the value is NULL. + * + * \since QGIS 3.12 + */ + static QByteArray getBinaryValue( const QVariant &value, QgsExpression *parent ) + { + if ( value.type() != QVariant::ByteArray ) + { + parent->setEvalErrorString( QObject::tr( "Value is not a binary value" ) ); + return QByteArray(); + } + return value.toByteArray(); + } + static double getDoubleValue( const QVariant &value, QgsExpression *parent ) { bool ok; diff --git a/tests/src/core/testqgsexpression.cpp b/tests/src/core/testqgsexpression.cpp index c1704e46bdd..a487dc19a1b 100644 --- a/tests/src/core/testqgsexpression.cpp +++ b/tests/src/core/testqgsexpression.cpp @@ -788,6 +788,10 @@ class TestQgsExpression: public QObject QTest::newRow( "Y coordinate to degree minute second" ) << "to_dms(6.3545681,'y',2)" << false << QVariant( "6°21′16.45″" ); // geometry functions + QTest::newRow( "geom_to_wkb" ) << "geom_to_wkt(geom_from_wkb(geom_to_wkb(make_point(4,5))))" << false << QVariant( "Point (4 5)" ); + QTest::newRow( "geom_to_wkb not geom" ) << "geom_to_wkt(geom_from_wkb(geom_to_wkb('a')))" << true << QVariant(); + QTest::newRow( "geom_from_wkb not geom" ) << "geom_to_wkt(geom_from_wkb(make_point(4,5)))" << true << QVariant(); + QTest::newRow( "geom_from_wkb null" ) << "geom_to_wkt(geom_from_wkb(NULL))" << false << QVariant(); QTest::newRow( "num_points" ) << "num_points(geom_from_wkt('GEOMETRYCOLLECTION(LINESTRING(0 0, 1 0),POINT(6 5))'))" << false << QVariant( 3 ); QTest::newRow( "num_interior_rings not geom" ) << "num_interior_rings('g')" << true << QVariant(); QTest::newRow( "num_interior_rings null" ) << "num_interior_rings(NULL)" << false << QVariant();