mirror of
https://github.com/qgis/QGIS.git
synced 2025-11-22 00:14:55 -05:00
[feature] New function "load_layer"
This function (available only in Processing expressions for now), allows loading a map layer via a source string and provider name. It is designed to allow use of the expression functions which directly reference map layers (such as the aggregate functions) with a hardcoded layer path, eg. then permitting these functions to be used outside of a project (such as via the qgis_process tool)
This commit is contained in:
parent
3cca02bb5d
commit
b23eb1d250
@ -388,6 +388,7 @@ Creates a new scope which contains functions relating to mesh layer element ``el
|
||||
};
|
||||
|
||||
|
||||
|
||||
/************************************************************************
|
||||
* This file has been generated automatically from *
|
||||
* *
|
||||
|
||||
19
resources/function_help/json/load_layer
Normal file
19
resources/function_help/json/load_layer
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "load_layer",
|
||||
"type": "function",
|
||||
"groups": ["Map Layers"],
|
||||
"description": "Loads a layer by source URI and provider name.",
|
||||
"arguments": [{
|
||||
"arg": "uri",
|
||||
"description": "layer source URI string"
|
||||
},
|
||||
{
|
||||
"arg": "provider",
|
||||
"description": "layer data provider name"
|
||||
}],
|
||||
"examples": [{
|
||||
"expression": "layer_property(load_layer('c:/data/roads.shp', 'ogr'), 'feature_count')",
|
||||
"returns": "count of features from the c:/data/roads.shp vector layer"
|
||||
}],
|
||||
"tags": ["layer", "vector", "raster", "mesh", "point", "cloud"]
|
||||
}
|
||||
@ -35,6 +35,9 @@
|
||||
#include "qgstriangularmesh.h"
|
||||
#include "qgsvectortileutils.h"
|
||||
#include "qgsmeshlayer.h"
|
||||
#include "qgsexpressionnodeimpl.h"
|
||||
#include "qgsproviderregistry.h"
|
||||
#include "qgsmaplayerfactory.h"
|
||||
|
||||
QgsExpressionContextScope *QgsExpressionContextUtils::globalScope()
|
||||
{
|
||||
@ -917,6 +920,7 @@ void QgsExpressionContextUtils::registerContextFunctions()
|
||||
QgsExpression::registerFunction( new GetProcessingParameterValue( QVariantMap() ) );
|
||||
QgsExpression::registerFunction( new GetCurrentFormFieldValue( ) );
|
||||
QgsExpression::registerFunction( new GetCurrentParentFormFieldValue( ) );
|
||||
QgsExpression::registerFunction( new LoadLayerFunction( ) );
|
||||
}
|
||||
|
||||
bool QgsScopedExpressionFunction::usesGeometry( const QgsExpressionNodeFunction *node ) const
|
||||
@ -1296,4 +1300,110 @@ QgsExpressionContextScope *QgsExpressionContextUtils::meshExpressionScope( QgsMe
|
||||
|
||||
return scope.release();
|
||||
}
|
||||
|
||||
|
||||
QVariant LoadLayerFunction::func( const QVariantList &, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * )
|
||||
{
|
||||
parent->setEvalErrorString( QObject::tr( "Invalid arguments for load_layer function" ) );
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
bool LoadLayerFunction::isStatic( const QgsExpressionNodeFunction *node, QgsExpression *parent, const QgsExpressionContext *context ) const
|
||||
{
|
||||
if ( node->args()->count() > 1 )
|
||||
{
|
||||
if ( !context )
|
||||
return false;
|
||||
|
||||
QPointer< QgsMapLayerStore > store( context->loadedLayerStore() );
|
||||
if ( !store )
|
||||
{
|
||||
parent->setEvalErrorString( QObject::tr( "load_layer cannot be used in this context" ) );
|
||||
return false;
|
||||
}
|
||||
|
||||
QgsExpressionNode *uriNode = node->args()->at( 0 );
|
||||
QgsExpressionNode *providerNode = node->args()->at( 1 );
|
||||
if ( !uriNode->isStatic( parent, context ) )
|
||||
{
|
||||
parent->setEvalErrorString( QObject::tr( "load_layer requires a static value for the uri argument" ) );
|
||||
return false;
|
||||
}
|
||||
if ( !providerNode->isStatic( parent, context ) )
|
||||
{
|
||||
parent->setEvalErrorString( QObject::tr( "load_layer requires a static value for the provider argument" ) );
|
||||
return false;
|
||||
}
|
||||
|
||||
const QString uri = uriNode->eval( parent, context ).toString();
|
||||
if ( uri.isEmpty() )
|
||||
{
|
||||
parent->setEvalErrorString( QObject::tr( "Invalid uri argument for load_layer" ) );
|
||||
return false;
|
||||
}
|
||||
|
||||
const QString providerKey = providerNode->eval( parent, context ).toString();
|
||||
if ( providerKey.isEmpty() )
|
||||
{
|
||||
parent->setEvalErrorString( QObject::tr( "Invalid provider argument for load_layer" ) );
|
||||
return false;
|
||||
}
|
||||
|
||||
const QgsCoordinateTransformContext transformContext = context->variable( QStringLiteral( "_project_transform_context" ) ).value<QgsCoordinateTransformContext>();
|
||||
|
||||
bool res = false;
|
||||
auto loadLayer = [ uri, providerKey, store, node, parent, &res, &transformContext ]
|
||||
{
|
||||
QgsProviderMetadata *metadata = QgsProviderRegistry::instance()->providerMetadata( providerKey );
|
||||
if ( !metadata )
|
||||
{
|
||||
parent->setEvalErrorString( QObject::tr( "Invalid provider argument for load_layer" ) );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( metadata->supportedLayerTypes().empty() )
|
||||
{
|
||||
parent->setEvalErrorString( QObject::tr( "Cannot use %1 provider for load_layer" ).arg( providerKey ) );
|
||||
return;
|
||||
}
|
||||
|
||||
QgsMapLayerFactory::LayerOptions layerOptions( transformContext );
|
||||
layerOptions.loadAllStoredStyles = false;
|
||||
layerOptions.loadDefaultStyle = false;
|
||||
|
||||
QgsMapLayer *layer = QgsMapLayerFactory::createLayer( uri, uri, metadata->supportedLayerTypes().value( 0 ), layerOptions, providerKey );
|
||||
if ( !layer )
|
||||
{
|
||||
parent->setEvalErrorString( QObject::tr( "Could not load_layer with uri: %1" ).arg( uri ) );
|
||||
return;
|
||||
}
|
||||
if ( !layer->isValid() )
|
||||
{
|
||||
delete layer;
|
||||
parent->setEvalErrorString( QObject::tr( "Could not load_layer with uri: %1" ).arg( uri ) );
|
||||
return;
|
||||
}
|
||||
|
||||
store->addMapLayer( layer );
|
||||
|
||||
node->setCachedStaticValue( QVariant::fromValue( QgsWeakMapLayerPointer( layer ) ) );
|
||||
res = true;
|
||||
};
|
||||
|
||||
// Make sure we load the layer on the thread where the store lives
|
||||
if ( QThread::currentThread() == store->thread() )
|
||||
loadLayer();
|
||||
else
|
||||
QMetaObject::invokeMethod( store, loadLayer, Qt::BlockingQueuedConnection );
|
||||
|
||||
return res;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
QgsScopedExpressionFunction *LoadLayerFunction::clone() const
|
||||
{
|
||||
return new LoadLayerFunction();
|
||||
}
|
||||
///@endcond
|
||||
|
||||
|
||||
@ -359,6 +359,24 @@ class CORE_EXPORT QgsExpressionContextUtils
|
||||
|
||||
};
|
||||
|
||||
///@cond PRIVATE
|
||||
#ifndef SIP_RUN
|
||||
class LoadLayerFunction : public QgsScopedExpressionFunction
|
||||
{
|
||||
public:
|
||||
LoadLayerFunction()
|
||||
: QgsScopedExpressionFunction( QStringLiteral( "load_layer" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "uri" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "provider" ) ), QStringLiteral( "Map Layers" ) )
|
||||
{}
|
||||
|
||||
QVariant func( const QVariantList &, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * ) override;
|
||||
bool isStatic( const QgsExpressionNodeFunction *node, QgsExpression *parent, const QgsExpressionContext *context ) const override;
|
||||
|
||||
QgsScopedExpressionFunction *clone() const override;
|
||||
|
||||
};
|
||||
#endif
|
||||
///@endcond
|
||||
|
||||
#ifndef SIP_RUN
|
||||
|
||||
/**
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
#include "qgsxmlutils.h"
|
||||
#include "qgsexpression.h"
|
||||
#include "qgsmaplayerstore.h"
|
||||
#include "qgsexpressioncontextutils.h"
|
||||
|
||||
const QString QgsExpressionContext::EXPR_FIELDS( QStringLiteral( "_fields_" ) );
|
||||
const QString QgsExpressionContext::EXPR_ORIGINAL_VALUE( QStringLiteral( "value" ) );
|
||||
@ -295,9 +296,15 @@ bool QgsExpressionContextScope::writeXml( QDomElement &element, QDomDocument &do
|
||||
// QgsExpressionContext
|
||||
//
|
||||
|
||||
QgsExpressionContext::QgsExpressionContext()
|
||||
{
|
||||
mLoadLayerFunction = std::make_unique< LoadLayerFunction >();
|
||||
}
|
||||
|
||||
QgsExpressionContext::QgsExpressionContext( const QList<QgsExpressionContextScope *> &scopes )
|
||||
: mStack( scopes )
|
||||
{
|
||||
mLoadLayerFunction = std::make_unique< LoadLayerFunction >();
|
||||
}
|
||||
|
||||
QgsExpressionContext::QgsExpressionContext( const QgsExpressionContext &other ) : mStack{}
|
||||
@ -311,6 +318,7 @@ QgsExpressionContext::QgsExpressionContext( const QgsExpressionContext &other )
|
||||
mCachedValues = other.mCachedValues;
|
||||
mFeedback = other.mFeedback;
|
||||
mDestinationStore = other.mDestinationStore;
|
||||
mLoadLayerFunction = std::make_unique< LoadLayerFunction >();
|
||||
}
|
||||
|
||||
QgsExpressionContext &QgsExpressionContext::operator=( QgsExpressionContext &&other ) noexcept
|
||||
@ -533,8 +541,10 @@ QString QgsExpressionContext::description( const QString &name ) const
|
||||
|
||||
bool QgsExpressionContext::hasFunction( const QString &name ) const
|
||||
{
|
||||
const auto constMStack = mStack;
|
||||
for ( const QgsExpressionContextScope *scope : constMStack )
|
||||
if ( name.compare( QLatin1String( "load_layer" ) ) == 0 && mDestinationStore )
|
||||
return true;
|
||||
|
||||
for ( const QgsExpressionContextScope *scope : mStack )
|
||||
{
|
||||
if ( scope->hasFunction( name ) )
|
||||
return true;
|
||||
@ -551,6 +561,10 @@ QStringList QgsExpressionContext::functionNames() const
|
||||
for ( const QString &name : functionNames )
|
||||
result.insert( name );
|
||||
}
|
||||
|
||||
if ( mDestinationStore )
|
||||
result.insert( QStringLiteral( "load_layer" ) );
|
||||
|
||||
QStringList listResult( result.constBegin(), result.constEnd() );
|
||||
listResult.sort();
|
||||
return listResult;
|
||||
@ -558,6 +572,11 @@ QStringList QgsExpressionContext::functionNames() const
|
||||
|
||||
QgsExpressionFunction *QgsExpressionContext::function( const QString &name ) const
|
||||
{
|
||||
if ( name.compare( QLatin1String( "load_layer" ) ) == 0 && mDestinationStore )
|
||||
{
|
||||
return mLoadLayerFunction.get();
|
||||
}
|
||||
|
||||
//iterate through stack backwards, so that higher priority variables take precedence
|
||||
QList< QgsExpressionContextScope * >::const_iterator it = mStack.constEnd();
|
||||
while ( it != mStack.constBegin() )
|
||||
|
||||
@ -29,6 +29,7 @@
|
||||
|
||||
class QgsReadWriteContext;
|
||||
class QgsMapLayerStore;
|
||||
class LoadLayerFunction;
|
||||
|
||||
/**
|
||||
* \ingroup core
|
||||
@ -481,7 +482,7 @@ class CORE_EXPORT QgsExpressionContext
|
||||
public:
|
||||
|
||||
//! Constructor for QgsExpressionContext
|
||||
QgsExpressionContext() = default;
|
||||
QgsExpressionContext();
|
||||
|
||||
/**
|
||||
* Initializes the context with given list of scopes.
|
||||
@ -936,6 +937,7 @@ class CORE_EXPORT QgsExpressionContext
|
||||
|
||||
QgsFeedback *mFeedback = nullptr;
|
||||
|
||||
std::unique_ptr< LoadLayerFunction > mLoadLayerFunction;
|
||||
QPointer< QgsMapLayerStore > mDestinationStore;
|
||||
|
||||
// Cache is mutable because we want to be able to add cached values to const contexts
|
||||
|
||||
@ -5650,6 +5650,66 @@ class TestQgsExpression: public QObject
|
||||
QCOMPARE( QgsExpressionUtils::toLocalizedString( QString( "hello world" ) ), QStringLiteral( "hello world" ) );
|
||||
}
|
||||
|
||||
void testLoadLayer()
|
||||
{
|
||||
QgsExpressionContext context;
|
||||
QgsMapLayerStore store;
|
||||
|
||||
// load_layer is only available when a destination store is set
|
||||
QVERIFY( !context.hasFunction( QStringLiteral( "load_layer" ) ) );
|
||||
QVERIFY( !context.functionNames().contains( QStringLiteral( "load_layer" ) ) );
|
||||
QVERIFY( !context.function( QStringLiteral( "load_layer" ) ) );
|
||||
|
||||
context.setLoadedLayerStore( &store );
|
||||
QVERIFY( context.hasFunction( QStringLiteral( "load_layer" ) ) );
|
||||
QVERIFY( context.functionNames().contains( QStringLiteral( "load_layer" ) ) );
|
||||
QVERIFY( context.function( QStringLiteral( "load_layer" ) ) );
|
||||
|
||||
const QString pointsFileName = QStringLiteral( TEST_DATA_DIR ) + '/' + "points.shp";
|
||||
QgsExpression exp( QStringLiteral( "layer_property(load_layer('%1', 'ogr'), 'feature_count')" ).arg( pointsFileName ) );
|
||||
QVERIFY( exp.prepare( &context ) );
|
||||
QVERIFY( !exp.hasEvalError() );
|
||||
QCOMPARE( exp.evaluate( &context ).toInt(), 17 );
|
||||
|
||||
// non-static arguments are not allowed
|
||||
QgsFields fields;
|
||||
fields.append( QgsField( QStringLiteral( "first_field" ), QVariant::Int ) );
|
||||
|
||||
QgsFeature f( fields );
|
||||
f.setAttributes( QgsAttributes() << 11 );
|
||||
context.setFields( fields );
|
||||
context.setFeature( f );
|
||||
|
||||
exp = QgsExpression( QStringLiteral( "layer_property(load_layer('%1' || \"first_field\", 'ogr'), 'feature_count')" ).arg( pointsFileName ) );
|
||||
QVERIFY( exp.prepare( &context ) );
|
||||
QVERIFY( exp.hasEvalError() );
|
||||
QCOMPARE( exp.evalErrorString(), QStringLiteral( "load_layer requires a static value for the uri argument" ) );
|
||||
|
||||
exp = QgsExpression( QStringLiteral( "layer_property(load_layer('%1', 'ogr' || \"first_field\"), 'feature_count')" ).arg( pointsFileName ) );
|
||||
QVERIFY( exp.prepare( &context ) );
|
||||
QVERIFY( exp.hasEvalError() );
|
||||
QCOMPARE( exp.evalErrorString(), QStringLiteral( "load_layer requires a static value for the provider argument" ) );
|
||||
|
||||
// invalid provider
|
||||
exp = QgsExpression( QStringLiteral( "layer_property(load_layer('%1', 'magic'), 'feature_count')" ).arg( pointsFileName ) );
|
||||
QVERIFY( exp.prepare( &context ) );
|
||||
QVERIFY( exp.hasEvalError() );
|
||||
QCOMPARE( exp.evalErrorString(), QStringLiteral( "Invalid provider argument for load_layer" ) );
|
||||
|
||||
// invalid uri
|
||||
exp = QgsExpression( QStringLiteral( "layer_property(load_layer('nope', 'ogr'), 'feature_count')" ) );
|
||||
QVERIFY( exp.prepare( &context ) );
|
||||
QVERIFY( exp.hasEvalError() );
|
||||
QCOMPARE( exp.evalErrorString(), QStringLiteral( "Could not load_layer with uri: nope" ) );
|
||||
|
||||
// raster layer
|
||||
const QString rasterFileName = QStringLiteral( TEST_DATA_DIR ) + '/' + "tenbytenraster.asc";
|
||||
exp = QgsExpression( QStringLiteral( "layer_property(load_layer('%1', 'gdal'), 'type')" ).arg( rasterFileName ) );
|
||||
QVERIFY( exp.prepare( &context ) );
|
||||
QVERIFY( !exp.hasEvalError() );
|
||||
QCOMPARE( exp.evaluate( &context ).toString(), QStringLiteral( "Raster" ) );
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
QGSTEST_MAIN( TestQgsExpression )
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user