Mesh expressions: add $vertex_as_point and $vertex_z functions (#44786)

* 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
This commit is contained in:
Vincent Cloarec 2021-08-24 14:56:16 -04:00 committed by GitHub
parent fb33167d9c
commit bc192a60b2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 227 additions and 0 deletions

View File

@ -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:

View File

@ -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;

View File

@ -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)'"}
]
}

View File

@ -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"}
]
}

View File

@ -0,0 +1,5 @@
{
"name": "Meshes",
"type": "group",
"description": "Contains functions which calculate or return mesh related values."
}

View File

@ -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

View File

@ -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

View File

@ -27,6 +27,7 @@
#include "qgsproject.h"
#include "qgsrelationmanager.h"
#include "qgsvectorlayer.h"
#include "qgsmeshlayer.h"
#include <QThread>
#include <QLocale>
@ -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 )

View File

@ -42,6 +42,7 @@
#include "qgslayermetadataformatter.h"
#include "qgsmesheditor.h"
#include "qgsmessagelog.h"
#include "qgsexpressioncontextutils.h"
QgsMeshLayer::QgsMeshLayer( const QString &meshLayerPath,
const QString &baseName,
@ -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 );

View File

@ -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
*

View File

@ -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:
@ -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() );
@ -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();

View File

@ -97,6 +97,8 @@ class TestQgsMeshLayer : public QObject
void testMdalProviderQuerySublayers();
void testMdalProviderQuerySublayersFastScan();
void testSelectByExpression();
};
QString TestQgsMeshLayer::readFile( const QString &fname ) const
@ -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;