Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Remove a bunch of layer variables, replace with new layer_property
function

layer_property takes a layer name/id and a property (eg 'crs')
and returns the matching value. This approach is more flexible than
having all these values as variables, since it allows retrieval
of properties of a layer from contexts which aren't layer-aware
(eg a composer label showing the metadata of a layer).
  • Loading branch information
nyalldawson committed Aug 24, 2015
1 parent 9f3e229 commit 7871d6c
Show file tree
Hide file tree
Showing 5 changed files with 176 additions and 43 deletions.
2 changes: 2 additions & 0 deletions resources/function_help/General
@@ -0,0 +1,2 @@
<h3>General</h3>
This group contains general assorted functions.
34 changes: 34 additions & 0 deletions resources/function_help/layer_property
@@ -0,0 +1,34 @@
<h3>layer_property function</h3>
Returns a matching layer property or metadata value.

<h4>Syntax</h4>
<pre>layer_property(layer,property)</pre>

<h4>Arguments</h4>
layer &rarr; a string, representing either a layer name or layer ID<br />
property &rarr; a string corresponding to the property to return. Valid options are:<br />
<ul>
<li>name: layer name</li>
<li>id: layer ID</li>
<li>title: metadata title string</li>
<li>abstract: metadata abstract string</li>
<li>keywords: metadata keywords</li>
<li>data_url: metadata URL</li>
<li>attribution: metadata attribution string</li>
<li>attribution_url: metadata attribution URL</li>
<li>source: layer source</li>
<li>min_scale: minimum display scale for layer</li>
<li>max_scale: maximum display scale for layer</li>
<li>crs: layer CRS</li>
<li>crs_definition: layer CRS full definition</li>
<li>extent: layer extent (as a geometry object)</li>
<li>type: layer type, eg Vector or Raster</li>
<li>storage_type: storage format (vector layers only)</li>
<li>geometry_type: geometry type, eg Point (vector layers only)</li>
<li>feature_count: approximate feature count for layer (vector layers only)</li>
</ul>
<h4>Example</h4>
<pre> layer_property('streets','title') &rarr; 'Basemap Streets'</pre><br />
<pre> layer_property('airports','feature_count') &rarr; 120</pre><br />
<pre> layer_property('landsat','crs') &rarr;'EPSG:4326'</pre>

104 changes: 86 additions & 18 deletions src/core/qgsexpression.cpp
Expand Up @@ -1685,6 +1685,88 @@ static QVariant fcnGetFeature( const QVariantList& values, const QgsExpressionCo
return QVariant();
}

static QVariant fcnGetLayerProperty( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent )
{
QString layerIdOrName = getStringValue( values.at( 0 ), parent );

//try to find a matching layer by name
QgsMapLayer* layer = QgsMapLayerRegistry::instance()->mapLayer( layerIdOrName ); //search by id first
if ( !layer )
{
QList<QgsMapLayer *> layersByName = QgsMapLayerRegistry::instance()->mapLayersByName( layerIdOrName );
if ( layersByName.size() > 0 )
{
layer = layersByName.at( 0 );
}
}

if ( !layer )
return QVariant();

QString layerProperty = getStringValue( values.at( 1 ), parent );
if ( QString::compare( layerProperty, QString( "name" ), Qt::CaseInsensitive ) == 0 )
return layer->name();
else if ( QString::compare( layerProperty, QString( "id" ), Qt::CaseInsensitive ) == 0 )
return layer->id();
else if ( QString::compare( layerProperty, QString( "title" ), Qt::CaseInsensitive ) == 0 )
return layer->title();
else if ( QString::compare( layerProperty, QString( "abstract" ), Qt::CaseInsensitive ) == 0 )
return layer->abstract();
else if ( QString::compare( layerProperty, QString( "keywords" ), Qt::CaseInsensitive ) == 0 )
return layer->keywordList();
else if ( QString::compare( layerProperty, QString( "data_url" ), Qt::CaseInsensitive ) == 0 )
return layer->dataUrl();
else if ( QString::compare( layerProperty, QString( "attribution" ), Qt::CaseInsensitive ) == 0 )
return layer->attribution();
else if ( QString::compare( layerProperty, QString( "attribution_url" ), Qt::CaseInsensitive ) == 0 )
return layer->attributionUrl();
else if ( QString::compare( layerProperty, QString( "source" ), Qt::CaseInsensitive ) == 0 )
return layer->publicSource();
else if ( QString::compare( layerProperty, QString( "min_scale" ), Qt::CaseInsensitive ) == 0 )
return ( double )layer->minimumScale();
else if ( QString::compare( layerProperty, QString( "max_scale" ), Qt::CaseInsensitive ) == 0 )
return ( double )layer->maximumScale();
else if ( QString::compare( layerProperty, QString( "crs" ), Qt::CaseInsensitive ) == 0 )
return layer->crs().authid();
else if ( QString::compare( layerProperty, QString( "crs_definition" ), Qt::CaseInsensitive ) == 0 )
return layer->crs().toProj4();
else if ( QString::compare( layerProperty, QString( "extent" ), Qt::CaseInsensitive ) == 0 )
{
QgsGeometry* extentGeom = QgsGeometry::fromRect( layer->extent() );
QVariant result = QVariant::fromValue( *extentGeom );
delete extentGeom;
return result;
}
else if ( QString::compare( layerProperty, QString( "type" ), Qt::CaseInsensitive ) == 0 )
{
switch ( layer->type() )
{
case QgsMapLayer::VectorLayer:
return QCoreApplication::translate( "expressions", "Vector" );
case QgsMapLayer::RasterLayer:
return QCoreApplication::translate( "expressions", "Raster" );
case QgsMapLayer::PluginLayer:
return QCoreApplication::translate( "expressions", "Plugin" );
}
}
else
{
//vector layer methods
QgsVectorLayer* vLayer = dynamic_cast< QgsVectorLayer* >( layer );
if ( vLayer )
{
if ( QString::compare( layerProperty, QString( "storage_type" ), Qt::CaseInsensitive ) == 0 )
return vLayer->storageType();
else if ( QString::compare( layerProperty, QString( "geometry_type" ), Qt::CaseInsensitive ) == 0 )
return QGis::vectorGeometryType( vLayer->geometryType() );
else if ( QString::compare( layerProperty, QString( "feature_count" ), Qt::CaseInsensitive ) == 0 )
return QVariant::fromValue( vLayer->featureCount() );
}
}

return QVariant();
}

bool QgsExpression::registerFunction( QgsExpression::Function* function )
{
int fnIdx = functionIndex( function->name() );
Expand Down Expand Up @@ -1753,7 +1835,7 @@ const QStringList& QgsExpression::BuiltinFunctions()
<< "transform" << "get_feature" << "getFeature"
<< "levenshtein" << "longest_common_substring" << "hamming_distance"
<< "soundex"
<< "attribute" << "var"
<< "attribute" << "var" << "layer_property"
<< "$rownum" << "$id" << "$scale" << "_specialcol_";
}
return gmBuiltinFunctions;
Expand Down Expand Up @@ -1884,7 +1966,8 @@ const QList<QgsExpression::Function*>& QgsExpression::Functions()
<< new StaticFunction( "$scale", 0, fcnScale, "Record" )
<< new StaticFunction( "uuid", 0, fcnUuid, "Record", QString(), false, QStringList(), false, QStringList() << "$uuid" )
<< new StaticFunction( "get_feature", 3, fcnGetFeature, "Record", QString(), false, QStringList(), false, QStringList() << "getFeature" )
<< new StaticFunction( "var", 1, fcnGetVariable, "Variables" )
<< new StaticFunction( "layer_property", 2, fcnGetLayerProperty, "General" )
<< new StaticFunction( "var", 1, fcnGetVariable, "General" )

//return all attributes string for referencedColumns - this is caught by
// QgsFeatureRequest::setSubsetOfAttributes and causes all attributes to be fetched by the
Expand Down Expand Up @@ -3046,22 +3129,6 @@ void QgsExpression::initVariableHelp()
//layer variables
gVariableHelpTexts.insert( "layer_name", QCoreApplication::translate( "variable_help", "Name of current layer." ) );
gVariableHelpTexts.insert( "layer_id", QCoreApplication::translate( "variable_help", "ID of current project." ) );
gVariableHelpTexts.insert( "layer_title", QCoreApplication::translate( "variable_help", "Title of current layer." ) );
gVariableHelpTexts.insert( "layer_abstract", QCoreApplication::translate( "variable_help", "Metadata abstract for current layer." ) );
gVariableHelpTexts.insert( "layer_keywords", QCoreApplication::translate( "variable_help", "Metadata keywords for current layer." ) );
gVariableHelpTexts.insert( "layer_dataurl", QCoreApplication::translate( "variable_help", "Metadata data URL for current layer." ) );
gVariableHelpTexts.insert( "layer_attribution", QCoreApplication::translate( "variable_help", "Metadata attribution for current layer." ) );
gVariableHelpTexts.insert( "layer_attributionurl", QCoreApplication::translate( "variable_help", "Metadata attribution URL for current layer." ) );
gVariableHelpTexts.insert( "layer_source", QCoreApplication::translate( "variable_help", "Source of current layer." ) );
gVariableHelpTexts.insert( "layer_minscale", QCoreApplication::translate( "variable_help", "Minimum display scale for current layer." ) );
gVariableHelpTexts.insert( "layer_maxscale", QCoreApplication::translate( "variable_help", "Maximum display scale for current layer." ) );
gVariableHelpTexts.insert( "layer_crs", QCoreApplication::translate( "variable_help", "CRS of current layer." ) );
gVariableHelpTexts.insert( "layer_crsdefinition", QCoreApplication::translate( "variable_help", "CRS definition of current layer." ) );
gVariableHelpTexts.insert( "layer_extent", QCoreApplication::translate( "variable_help", "Extent of current layer (as a geometry object)." ) );
gVariableHelpTexts.insert( "layer_type", QCoreApplication::translate( "variable_help", "Type of current layer, eg raster/vector." ) );
gVariableHelpTexts.insert( "layer_storagetype", QCoreApplication::translate( "variable_help", "Storage format of current layer." ) );
gVariableHelpTexts.insert( "layer_geometrytype", QCoreApplication::translate( "variable_help", "Geometry type of current layer." ) );
gVariableHelpTexts.insert( "layer_featurecount", QCoreApplication::translate( "variable_help", "Approximate feature count for current layer." ) );

//composition variables
gVariableHelpTexts.insert( "layout_numpages", QCoreApplication::translate( "variable_help", "Number of pages in composition." ) );
Expand Down Expand Up @@ -3116,6 +3183,7 @@ QString QgsExpression::group( QString name )
{
if ( gGroups.isEmpty() )
{
gGroups.insert( "General", QObject::tr( "General" ) );
gGroups.insert( "Operators", QObject::tr( "Operators" ) );
gGroups.insert( "Conditionals", QObject::tr( "Conditionals" ) );
gGroups.insert( "Fields and Values", QObject::tr( "Fields and Values" ) );
Expand Down
27 changes: 2 additions & 25 deletions src/core/qgsexpressioncontext.cpp
Expand Up @@ -571,35 +571,12 @@ QgsExpressionContextScope* QgsExpressionContextUtils::layerScope( const QgsMapLa
scope->setVariable( variableName, varValue );
}

//TODO - add tests for all of these:
scope->addVariable( QgsExpressionContextScope::StaticVariable( "layer_name", layer->name(), true ) );
scope->addVariable( QgsExpressionContextScope::StaticVariable( "layer_id", layer->id(), true ) );
scope->addVariable( QgsExpressionContextScope::StaticVariable( "layer_title", layer->title(), true ) );
scope->addVariable( QgsExpressionContextScope::StaticVariable( "layer_abstract", layer->abstract(), true ) );
scope->addVariable( QgsExpressionContextScope::StaticVariable( "layer_keywords", layer->keywordList(), true ) );
scope->addVariable( QgsExpressionContextScope::StaticVariable( "layer_dataurl", layer->dataUrl(), true ) );
scope->addVariable( QgsExpressionContextScope::StaticVariable( "layer_attribution", layer->attribution(), true ) );
scope->addVariable( QgsExpressionContextScope::StaticVariable( "layer_attributionurl", layer->attributionUrl(), true ) );
scope->addVariable( QgsExpressionContextScope::StaticVariable( "layer_source", layer->publicSource(), true ) );
scope->addVariable( QgsExpressionContextScope::StaticVariable( "layer_minscale", layer->minimumScale(), true ) );
scope->addVariable( QgsExpressionContextScope::StaticVariable( "layer_maxscale", layer->maximumScale(), true ) );
scope->addVariable( QgsExpressionContextScope::StaticVariable( "layer_crs", layer->crs().authid(), true ) );
scope->addVariable( QgsExpressionContextScope::StaticVariable( "layer_crsdefinition", layer->crs().toProj4(), true ) );

//some methods we want aren't const
QgsMapLayer* nonConstLayer = const_cast< QgsMapLayer* >( layer );
QgsGeometry* extentGeom = QgsGeometry::fromRect( nonConstLayer->extent() );
scope->addVariable( QgsExpressionContextScope::StaticVariable( "layer_extent", QVariant::fromValue( *extentGeom ), true ) );
delete extentGeom;

QgsVectorLayer* vLayer = dynamic_cast< QgsVectorLayer* >( nonConstLayer );

const QgsVectorLayer* vLayer = dynamic_cast< const QgsVectorLayer* >( layer );
if ( vLayer )
{
scope->addVariable( QgsExpressionContextScope::StaticVariable( "layer_type", vLayer->type(), true ) );
scope->addVariable( QgsExpressionContextScope::StaticVariable( "layer_storagetype", vLayer->storageType(), true ) );
QString typeString( QGis::vectorGeometryType( vLayer->geometryType() ) );
scope->addVariable( QgsExpressionContextScope::StaticVariable( "layer_geometrytype", typeString, true ) );
scope->addVariable( QgsExpressionContextScope::StaticVariable( "layer_featurecount", QVariant::fromValue( vLayer->featureCount() ), true ) );
scope->setFields( vLayer->fields() );
}

Expand Down
52 changes: 52 additions & 0 deletions tests/src/core/testqgsexpression.cpp
Expand Up @@ -26,6 +26,8 @@
#include <qgsgeometry.h>
#include <qgsrenderchecker.h>
#include "qgsexpressioncontext.h"
#include "qgsvectorlayer.h"
#include "qgsmaplayerregistry.h"

static void _parseAndEvalExpr( int arg )
{
Expand All @@ -40,6 +42,17 @@ static void _parseAndEvalExpr( int arg )
class TestQgsExpression: public QObject
{
Q_OBJECT

public:

TestQgsExpression()
: mPointsLayer( 0 )
{}

private:

QgsVectorLayer* mPointsLayer;

private slots:

void initTestCase()
Expand All @@ -53,6 +66,22 @@ class TestQgsExpression: public QObject
// Will make sure the settings dir with the style file for color ramp is created
QgsApplication::createDB();
QgsApplication::showSettings();

//create a point layer that will be used in all tests...
QString testDataDir = QString( TEST_DATA_DIR ) + "/";
QString pointsFileName = testDataDir + "points.shp";
QFileInfo pointFileInfo( pointsFileName );
mPointsLayer = new QgsVectorLayer( pointFileInfo.filePath(),
pointFileInfo.completeBaseName(), "ogr" );
QgsMapLayerRegistry::instance()->addMapLayer( mPointsLayer );
mPointsLayer->setTitle( "layer title" );
mPointsLayer->setAbstract( "layer abstract" );
mPointsLayer->setKeywordList( "layer,keywords" );
mPointsLayer->setDataUrl( "data url" );
mPointsLayer->setAttribution( "layer attribution" );
mPointsLayer->setAttributionUrl( "attribution url" );
mPointsLayer->setMaximumScale( 500 );
mPointsLayer->setMinimumScale( 1000 );
}

void cleanupTestCase()
Expand Down Expand Up @@ -465,6 +494,29 @@ class TestQgsExpression: public QObject
QTest::newRow( "brackets first" ) << "(1+2)*(3+4)" << false << QVariant( 21 );
QTest::newRow( "right associativity" ) << "(2^3)^2" << false << QVariant( 64. );
QTest::newRow( "left associativity" ) << "1-(2-1)" << false << QVariant( 0 );

// layer_property tests
QTest::newRow( "layer_property no layer" ) << "layer_property('','title')" << false << QVariant();
QTest::newRow( "layer_property bad layer" ) << "layer_property('bad','title')" << false << QVariant();
QTest::newRow( "layer_property no property" ) << QString( "layer_property('%1','')" ).arg( mPointsLayer->name() ) << false << QVariant();
QTest::newRow( "layer_property bad property" ) << QString( "layer_property('%1','bad')" ).arg( mPointsLayer->name() ) << false << QVariant();
QTest::newRow( "layer_property by id" ) << QString( "layer_property('%1','name')" ).arg( mPointsLayer->id() ) << false << QVariant( mPointsLayer->name() );
QTest::newRow( "layer_property name" ) << QString( "layer_property('%1','name')" ).arg( mPointsLayer->name() ) << false << QVariant( mPointsLayer->name() );
QTest::newRow( "layer_property id" ) << QString( "layer_property('%1','id')" ).arg( mPointsLayer->name() ) << false << QVariant( mPointsLayer->id() );
QTest::newRow( "layer_property title" ) << QString( "layer_property('%1','title')" ).arg( mPointsLayer->name() ) << false << QVariant( mPointsLayer->title() );
QTest::newRow( "layer_property abstract" ) << QString( "layer_property('%1','abstract')" ).arg( mPointsLayer->name() ) << false << QVariant( mPointsLayer->abstract() );
QTest::newRow( "layer_property keywords" ) << QString( "layer_property('%1','keywords')" ).arg( mPointsLayer->name() ) << false << QVariant( mPointsLayer->keywordList() );
QTest::newRow( "layer_property data_url" ) << QString( "layer_property('%1','data_url')" ).arg( mPointsLayer->name() ) << false << QVariant( mPointsLayer->dataUrl() );
QTest::newRow( "layer_property attribution" ) << QString( "layer_property('%1','attribution')" ).arg( mPointsLayer->name() ) << false << QVariant( mPointsLayer->attribution() );
QTest::newRow( "layer_property attribution_url" ) << QString( "layer_property('%1','attribution_url')" ).arg( mPointsLayer->name() ) << false << QVariant( mPointsLayer->attributionUrl() );
QTest::newRow( "layer_property source" ) << QString( "layer_property('%1','source')" ).arg( mPointsLayer->name() ) << false << QVariant( mPointsLayer->publicSource() );
QTest::newRow( "layer_property min_scale" ) << QString( "layer_property('%1','min_scale')" ).arg( mPointsLayer->name() ) << false << QVariant(( double )mPointsLayer->minimumScale() );
QTest::newRow( "layer_property max_scale" ) << QString( "layer_property('%1','max_scale')" ).arg( mPointsLayer->name() ) << false << QVariant(( double )mPointsLayer->maximumScale() );
QTest::newRow( "layer_property crs" ) << QString( "layer_property('%1','crs')" ).arg( mPointsLayer->name() ) << false << QVariant( "EPSG:4326" );
QTest::newRow( "layer_property extent" ) << QString( "geom_to_wkt(layer_property('%1','extent'))" ).arg( mPointsLayer->name() ) << false << QVariant( "Polygon ((-118.88888889 22.80020704, -83.33333333 22.80020704, -83.33333333 46.87198068, -118.88888889 46.87198068, -118.88888889 22.80020704))" );
QTest::newRow( "layer_property type" ) << QString( "layer_property('%1','type')" ).arg( mPointsLayer->name() ) << false << QVariant( "Vector" );
QTest::newRow( "layer_property storage_type" ) << QString( "layer_property('%1','storage_type')" ).arg( mPointsLayer->name() ) << false << QVariant( "ESRI Shapefile" );
QTest::newRow( "layer_property geometry_type" ) << QString( "layer_property('%1','geometry_type')" ).arg( mPointsLayer->name() ) << false << QVariant( "Point" );
}

void run_evaluation_test( QgsExpression& exp, bool evalError, QVariant& result )
Expand Down

0 comments on commit 7871d6c

Please sign in to comment.