Skip to content

Commit

Permalink
Mesh expressions: add $vertex_as_point and $vertex_z functions (#44786)
Browse files Browse the repository at this point in the history
* mesh expression $vertex_as_point $vertex_Z_value

* functions help

* SIP, doc and indentation

* fix SIP

* fix help file

* change function name and add Meshes group description

* fix typo

* fix strings and docs

* typo
  • Loading branch information
vcloarec committed Aug 24, 2021
1 parent fb33167 commit bc192a6
Show file tree
Hide file tree
Showing 12 changed files with 227 additions and 0 deletions.
Expand Up @@ -365,6 +365,13 @@ Creates a new scope which contains variables and functions relating to provider
static void registerContextFunctions();
%Docstring
Registers all known core functions provided by :py:class:`QgsExpressionContextScope` objects.
%End

static QgsExpressionContextScope *meshExpressionScope() /Factory/;
%Docstring
Creates a new scope which contains functions relating to mesh layer elements (face, vertex, ...)

.. versionadded:: 3.22
%End

public:
Expand Down
10 changes: 10 additions & 0 deletions python/core/auto_generated/mesh/qgsmeshlayer.sip.in
Expand Up @@ -658,6 +658,16 @@ The returned position is in map coordinates.


.. versionadded:: 3.14
%End

QList<int> selectVerticesByExpression( const QString &expression, const QgsExpressionContext &expressionContext = QgsExpressionContext() );
%Docstring
Returns a list of vertex indexes that meet the condition defined by ``expression`` with the context ``expressionContext``

To express the relation with a vertex, the expression can be defined with function returning value
linked to the current vertex, like " $vertex_z ", "$vertex_as_point"

.. versionadded:: 3.22
%End

QgsMeshDatasetGroupTreeItem *datasetGroupTreeRootItem() const;
Expand Down
8 changes: 8 additions & 0 deletions resources/function_help/json/$vertex_as_point
@@ -0,0 +1,8 @@
{
"name": "$vertex_as_point",
"type": "function",
"groups": ["Meshes"],
"description": "Returns the current vertex as a point geometry.",
"examples": [ { "expression":"geom_to_wkt( $vertex_as_point )", "returns":"'POINT(800 1500 41)'"}
]
}
8 changes: 8 additions & 0 deletions resources/function_help/json/$vertex_z
@@ -0,0 +1,8 @@
{
"name": "$vertex_z",
"type": "function",
"groups": ["Meshes"],
"description": "Returns the Z value of the current mesh vertex.",
"examples": [ { "expression":"$vertex_z", "returns":"42"}
]
}
5 changes: 5 additions & 0 deletions resources/function_help/json/Meshes
@@ -0,0 +1,5 @@
{
"name": "Meshes",
"type": "group",
"description": "Contains functions which calculate or return mesh related values."
}
94 changes: 94 additions & 0 deletions src/core/expression/qgsexpressioncontextutils.cpp
Expand Up @@ -975,3 +975,97 @@ QgsScopedExpressionFunction *QgsExpressionContextUtils::GetLayerVisibility::clon
func->mScaleBasedVisibilityDetails = mScaleBasedVisibilityDetails;
return func;
}

//
// mesh expression context
//

/// @cond PRIVATE
class CurrentVertexZValueExpressionFunction: public QgsScopedExpressionFunction
{
public:
CurrentVertexZValueExpressionFunction():
QgsScopedExpressionFunction( "$vertex_z",
0,
QStringLiteral( "Meshes" ) )
{}

QgsScopedExpressionFunction *clone() const override {return new CurrentVertexZValueExpressionFunction();}

QVariant func( const QVariantList &, const QgsExpressionContext *context, QgsExpression *, const QgsExpressionNodeFunction * ) override
{
if ( !context )
return QVariant();

if ( !context->hasVariable( QStringLiteral( "_mesh_vertex_index" ) ) || !context->hasVariable( QStringLiteral( "_mesh_layer" ) ) )
return QVariant();

int vertexIndex = context->variable( QStringLiteral( "_mesh_vertex_index" ) ).toInt();

QgsMeshLayer *layer = qobject_cast<QgsMeshLayer *>( qvariant_cast<QgsMapLayer *>( context->variable( QStringLiteral( "_mesh_layer" ) ) ) );
if ( !layer || !layer->nativeMesh() || layer->nativeMesh()->vertexCount() <= vertexIndex )
return QVariant();

const QgsMeshVertex &vertex = layer->nativeMesh()->vertex( vertexIndex );
if ( !vertex.isEmpty() )
return vertex.z();
else
return QVariant();
}

bool isStatic( const QgsExpressionNodeFunction *, QgsExpression *, const QgsExpressionContext * ) const override
{
return false;
}
};

class CurrentVertexExpressionFunction: public QgsScopedExpressionFunction
{
public:
CurrentVertexExpressionFunction():
QgsScopedExpressionFunction( "$vertex_as_point",
0,
QStringLiteral( "Meshes" ) )
{}

QgsScopedExpressionFunction *clone() const override {return new CurrentVertexExpressionFunction();}

QVariant func( const QVariantList &, const QgsExpressionContext *context, QgsExpression *, const QgsExpressionNodeFunction * ) override
{
if ( !context )
return QVariant();

if ( !context->hasVariable( QStringLiteral( "_mesh_vertex_index" ) ) || !context->hasVariable( QStringLiteral( "_mesh_layer" ) ) )
return QVariant();

int vertexIndex = context->variable( QStringLiteral( "_mesh_vertex_index" ) ).toInt();

QgsMeshLayer *layer = qobject_cast<QgsMeshLayer *>( qvariant_cast<QgsMapLayer *>( context->variable( QStringLiteral( "_mesh_layer" ) ) ) );
if ( !layer || !layer->nativeMesh() || layer->nativeMesh()->vertexCount() <= vertexIndex )
return QVariant();

const QgsMeshVertex &vertex = layer->nativeMesh()->vertex( vertexIndex );
if ( !vertex.isEmpty() )
return QVariant::fromValue( QgsGeometry( new QgsPoint( vertex ) ) );
else
return QVariant();
}

bool isStatic( const QgsExpressionNodeFunction *, QgsExpression *, const QgsExpressionContext * ) const override
{
return false;
}
};

QgsExpressionContextScope *QgsExpressionContextUtils::meshExpressionScope()
{
QgsExpression::registerFunction( new CurrentVertexExpressionFunction, true );
QgsExpression::registerFunction( new CurrentVertexZValueExpressionFunction, true );

std::unique_ptr<QgsExpressionContextScope> scope = std::make_unique<QgsExpressionContextScope>();
scope->addFunction( "$vertex_as_point", new CurrentVertexExpressionFunction );
scope->addFunction( "$vertex_z", new CurrentVertexZValueExpressionFunction );

return scope.release();
}
///@endcond
6 changes: 6 additions & 0 deletions src/core/expression/qgsexpressioncontextutils.h
Expand Up @@ -321,6 +321,12 @@ class CORE_EXPORT QgsExpressionContextUtils
*/
static void registerContextFunctions();

/**
* Creates a new scope which contains functions relating to mesh layer elements (face, vertex, ...)
* \since QGIS 3.22
*/
static QgsExpressionContextScope *meshExpressionScope() SIP_FACTORY;

private:

class GetLayerVisibility : public QgsScopedExpressionFunction
Expand Down
6 changes: 6 additions & 0 deletions src/core/expression/qgsexpressionutils.h
Expand Up @@ -27,6 +27,7 @@
#include "qgsproject.h"
#include "qgsrelationmanager.h"
#include "qgsvectorlayer.h"
#include "qgsmeshlayer.h"

#include <QThread>
#include <QLocale>
Expand Down Expand Up @@ -405,6 +406,11 @@ class QgsExpressionUtils
return qobject_cast<QgsRasterLayer *>( getMapLayer( value, e ) );
}

static QgsMeshLayer *getMeshLayer( const QVariant &value, QgsExpression *e )
{
return qobject_cast<QgsMeshLayer *>( getMapLayer( value, e ) );
}

static QVariantList getListValue( const QVariant &value, QgsExpression *parent )
{
if ( value.type() == QVariant::List || value.type() == QVariant::StringList )
Expand Down
35 changes: 35 additions & 0 deletions src/core/mesh/qgsmeshlayer.cpp
Expand Up @@ -42,6 +42,7 @@
#include "qgslayermetadataformatter.h"
#include "qgsmesheditor.h"
#include "qgsmessagelog.h"
#include "qgsexpressioncontextutils.h"

QgsMeshLayer::QgsMeshLayer( const QString &meshLayerPath,
const QString &baseName,
Expand Down Expand Up @@ -1138,6 +1139,40 @@ QgsPointXY QgsMeshLayer::snapOnElement( QgsMesh::ElementType elementType, const
return QgsPointXY(); // avoid warnings
}

QList<int> QgsMeshLayer::selectVerticesByExpression( const QString &expressionString, const QgsExpressionContext &expressionContext )
{
if ( !mNativeMesh )
{
// lazy loading of mesh data
fillNativeMesh();
}

QList<int> ret;

if ( !mNativeMesh )
return ret;

QgsExpression expression( expressionString );
QgsExpressionContext context = expressionContext;

std::unique_ptr<QgsExpressionContextScope> expScope( QgsExpressionContextUtils::meshExpressionScope() );

context.appendScope( expScope.release() );
context.lastScope()->setVariable( QStringLiteral( "_mesh_layer" ), QVariant::fromValue( this ) );

expression.prepare( &context );

for ( int i = 0; i < mNativeMesh->vertexCount(); ++i )
{
context.lastScope()->setVariable( QStringLiteral( "_mesh_vertex_index" ), i, false );

if ( expression.evaluate( &context ).toBool() )
ret.append( i );
}

return ret;
}

QgsMeshDatasetIndex QgsMeshLayer::staticScalarDatasetIndex() const
{
return QgsMeshDatasetIndex( mRendererSettings.activeScalarDatasetGroup(), mStaticScalarDatasetIndex );
Expand Down
10 changes: 10 additions & 0 deletions src/core/mesh/qgsmeshlayer.h
Expand Up @@ -670,6 +670,16 @@ class CORE_EXPORT QgsMeshLayer : public QgsMapLayer
*/
QgsPointXY snapOnElement( QgsMesh::ElementType elementType, const QgsPointXY &point, double searchRadius );

/**
* Returns a list of vertex indexes that meet the condition defined by \a expression with the context \a expressionContext
*
* To express the relation with a vertex, the expression can be defined with function returning value
* linked to the current vertex, like " $vertex_z ", "$vertex_as_point"
*
* \since QGIS 3.22
*/
QList<int> selectVerticesByExpression( const QString &expression, const QgsExpressionContext &expressionContext = QgsExpressionContext() );

/**
* Returns the root items of the dataset group tree item
*
Expand Down
23 changes: 23 additions & 0 deletions tests/src/core/testqgsexpression.cpp
Expand Up @@ -65,6 +65,7 @@ class TestQgsExpression: public QObject
QgsVectorLayer *mChildLayer2 = nullptr; // relation with composite keys
QgsVectorLayer *mChildLayer = nullptr;
QgsRasterLayer *mRasterLayer = nullptr;
QgsMeshLayer *mMeshLayer = nullptr;

private slots:

Expand Down Expand Up @@ -123,6 +124,11 @@ class TestQgsExpression: public QObject
rasterFileInfo.completeBaseName() );
QgsProject::instance()->addMapLayer( mRasterLayer );

QString meshFileName = testDataDir + "/mesh/quad_flower.2dm";
mMeshLayer = new QgsMeshLayer( meshFileName, "mesh layer", "mdal" );
mMeshLayer->updateTriangularMesh();
QgsProject::instance()->addMapLayer( mMeshLayer );

// test memory layer for get_feature tests
mMemoryLayer = new QgsVectorLayer( QStringLiteral( "Point?field=col1:integer&field=col2:string" ), QStringLiteral( "test" ), QStringLiteral( "memory" ) );
QVERIFY( mMemoryLayer->isValid() );
Expand Down Expand Up @@ -257,6 +263,23 @@ class TestQgsExpression: public QObject
QgsProject::instance()->relationManager()->addRelation( rel_ck );
}

void evalMeshElement()
{
QgsExpressionContext context;
context.appendScope( QgsExpressionContextUtils::meshExpressionScope() );
context.lastScope()->setVariable( QStringLiteral( "_mesh_vertex_index" ), 2 );
context.lastScope()->setVariable( QStringLiteral( "_mesh_layer" ), QVariant::fromValue( mMeshLayer ) );

QgsExpression expression( QStringLiteral( "$vertex_z" ) );
QCOMPARE( expression.evaluate( &context ).toDouble(), 800.0 );

expression = QgsExpression( QStringLiteral( "$vertex_as_point" ) );
QVariant out = expression.evaluate( &context );
QgsGeometry outGeom = out.value<QgsGeometry>();
QgsGeometry geom( new QgsPoint( 2500, 2500, 800 ) );
QCOMPARE( geom.equals( outGeom ), true );
}

void cleanupTestCase()
{
QgsApplication::exitQgis();
Expand Down
15 changes: 15 additions & 0 deletions tests/src/core/testqgsmeshlayer.cpp
Expand Up @@ -97,6 +97,8 @@ class TestQgsMeshLayer : public QObject

void testMdalProviderQuerySublayers();
void testMdalProviderQuerySublayersFastScan();

void testSelectByExpression();
};

QString TestQgsMeshLayer::readFile( const QString &fname ) const
Expand Down Expand Up @@ -1707,6 +1709,19 @@ void TestQgsMeshLayer::testMdalProviderQuerySublayersFastScan()
QVERIFY( res.at( 0 ).skippedContainerScan() );
}

void TestQgsMeshLayer::testSelectByExpression()
{
mMdalLayer->updateTriangularMesh();
QgsExpressionContext expressionContext;

QList<int> selectedVerticesIndexes = mMdalLayer->selectVerticesByExpression( QStringLiteral( " $vertex_z > 30" ) );
QCOMPARE( selectedVerticesIndexes, QList( {2, 3} ) );

selectedVerticesIndexes = mMdalLayer->selectVerticesByExpression( QStringLiteral( " x($vertex_as_point) > 1500" ) );
QCOMPARE( selectedVerticesIndexes.count(), 3 );
QCOMPARE( selectedVerticesIndexes, QList( {1, 2, 3} ) );
}

void TestQgsMeshLayer::test_temporal()
{
const qint64 relativeTime_0 = -1000;
Expand Down

0 comments on commit bc192a6

Please sign in to comment.