Skip to content

Commit

Permalink
Expression variable for the currently rendered part
Browse files Browse the repository at this point in the history
During rendering, two new variables will be available:

  * `geometry_part_count`
  * `geometry_part_num` (1-based index)

Useful to apply different styles to different parts of multipart
features
  • Loading branch information
m-kuhn committed Jan 13, 2016
1 parent 3da051b commit 4e9afce
Show file tree
Hide file tree
Showing 12 changed files with 228 additions and 9 deletions.
5 changes: 5 additions & 0 deletions python/core/qgsexpressioncontext.sip
Expand Up @@ -344,6 +344,11 @@ class QgsExpressionContext
*/
void appendScope( QgsExpressionContextScope* scope /Transfer/ );

/**
* Remove the last scope from the expression context.
*/
void popScope();

/** Appends a scope to the end of the context. This scope will override
* any matching variables or functions provided by existing scopes within the
* context. Ownership of the scope is transferred to the stack.
Expand Down
22 changes: 22 additions & 0 deletions python/core/symbology-ng/qgssymbolv2.sip
Expand Up @@ -191,6 +191,13 @@ class QgsSymbolV2
*/
void renderFeature( const QgsFeature& feature, QgsRenderContext& context, int layer = -1, bool selected = false, bool drawVertexMarker = false, int currentVertexMarkerType = 0, int currentVertexMarkerSize = 0 );

/**
* Returns the symbol render context. Only valid between startRender and stopRender calls.
*
* @return The symbol render context
*/
QgsSymbolV2RenderContext* symbolRenderContext();

protected:
QgsSymbolV2( SymbolType type, const QgsSymbolLayerV2List& layers /Transfer/ ); // can't be instantiated

Expand Down Expand Up @@ -283,6 +290,21 @@ class QgsSymbolV2RenderContext

// workaround for sip 4.7. Don't use assignment - will fail with assertion error
// QgsSymbolV2RenderContext& operator=( const QgsSymbolV2RenderContext& );

/**
* This scope is always available when a symbol of this type is being rendered.
*
* @return An expression scope for details about this symbol
*/
QgsExpressionContextScope* expressionContextScope();
/**
* Set an expression scope for this symbol.
*
* Will take ownership.
*
* @param contextScope An expression scope for details about this symbol
*/
void setExpressionContextScope( QgsExpressionContextScope* contextScope /Transfer/);
};


Expand Down
6 changes: 6 additions & 0 deletions src/core/qgsexpressioncontext.cpp
Expand Up @@ -381,6 +381,12 @@ void QgsExpressionContext::appendScope( QgsExpressionContextScope* scope )
mStack.append( scope );
}

void QgsExpressionContext::popScope()
{
if ( !mStack.isEmpty() )
mStack.pop_back();
}

QgsExpressionContext& QgsExpressionContext::operator<<( QgsExpressionContextScope* scope )
{
mStack.append( scope );
Expand Down
5 changes: 5 additions & 0 deletions src/core/qgsexpressioncontext.h
Expand Up @@ -378,6 +378,11 @@ class CORE_EXPORT QgsExpressionContext
*/
void appendScope( QgsExpressionContextScope* scope );

/**
* Remove the last scope from the expression context.
*/
void popScope();

/** Appends a scope to the end of the context. This scope will override
* any matching variables or functions provided by existing scopes within the
* context. Ownership of the scope is transferred to the stack.
Expand Down
13 changes: 8 additions & 5 deletions src/core/symbology-ng/qgsgeometrygeneratorsymbollayerv2.cpp
Expand Up @@ -186,17 +186,20 @@ bool QgsGeometryGeneratorSymbolLayerV2::isCompatibleWithSymbol( QgsSymbolV2* sym
Q_UNUSED( symbol )
return true;
}

void QgsGeometryGeneratorSymbolLayerV2::render( QgsSymbolV2RenderContext& context )
{
QgsGeometry geom = mExpression->evaluate( &context.renderContext().expressionContext() ).value<QgsGeometry>();
if ( context.feature() )
{
QgsFeature f = *context.feature();
QgsExpressionContext& expressionContext = context.renderContext().expressionContext();

QgsFeature f = expressionContext.feature();
QgsGeometry geom = mExpression->evaluate( &expressionContext ).value<QgsGeometry>();
f.setGeometry( geom );

QgsExpressionContextScope* subSymbolExpressionContextScope = mSymbol->symbolRenderContext()->expressionContextScope();

subSymbolExpressionContextScope->setFeature( f );

mSymbol->renderFeature( f, context.renderContext() );
}
}


46 changes: 42 additions & 4 deletions src/core/symbology-ng/qgssymbolv2.cpp
Expand Up @@ -88,6 +88,7 @@ QgsSymbolV2::QgsSymbolV2( SymbolType type, const QgsSymbolLayerV2List& layers )
, mRenderHints( 0 )
, mClipFeaturesToExtent( true )
, mLayer( nullptr )
, mSymbolRenderContext( nullptr )
{

// check they're all correct symbol layers
Expand Down Expand Up @@ -235,6 +236,7 @@ const unsigned char* QgsSymbolV2::_getPolygon( QPolygonF& pts, QList<QPolygonF>&

QgsSymbolV2::~QgsSymbolV2()
{
delete mSymbolRenderContext;
// delete all symbol layers (we own them, so it's okay)
qDeleteAll( mLayers );
}
Expand Down Expand Up @@ -434,19 +436,27 @@ bool QgsSymbolV2::changeSymbolLayer( int index, QgsSymbolLayerV2* layer )

void QgsSymbolV2::startRender( QgsRenderContext& context, const QgsFields* fields )
{
delete mSymbolRenderContext;
mSymbolRenderContext = new QgsSymbolV2RenderContext( context, outputUnit(), mAlpha, false, mRenderHints, nullptr, fields, mapUnitScale() );

QgsSymbolV2RenderContext symbolContext( context, outputUnit(), mAlpha, false, mRenderHints, nullptr, fields, mapUnitScale() );

QgsExpressionContextScope* scope = new QgsExpressionContextScope( QApplication::translate( "QgsSymbolV2", "Symbol Scope" ) );

mSymbolRenderContext->setExpressionContextScope( scope );

Q_FOREACH ( QgsSymbolLayerV2* layer, mLayers )
layer->startRender( symbolContext );
}

void QgsSymbolV2::stopRender( QgsRenderContext& context )
{
QgsSymbolV2RenderContext symbolContext( context, outputUnit(), mAlpha, false, mRenderHints, nullptr, nullptr, mapUnitScale() );

Q_UNUSED( context )
Q_FOREACH ( QgsSymbolLayerV2* layer, mLayers )
layer->stopRender( symbolContext );
layer->stopRender( *mSymbolRenderContext );

delete mSymbolRenderContext;
mSymbolRenderContext = nullptr;

mLayer = nullptr;
}
Expand Down Expand Up @@ -701,6 +711,10 @@ void QgsSymbolV2::renderFeature( const QgsFeature& feature, QgsRenderContext& co
deleteSegmentizedGeometry = true;
}

context.expressionContext().appendScope( mSymbolRenderContext->expressionContextScope() );
mSymbolRenderContext->expressionContextScope()->setVariable( "geometry_part_count", segmentizedGeometry->geometry()->partCount() );
mSymbolRenderContext->expressionContextScope()->setVariable( "geometry_part_num", 1 );

switch ( QgsWKBTypes::flatType( segmentizedGeometry->geometry()->wkbType() ) )
{
case QgsWKBTypes::Point:
Expand Down Expand Up @@ -766,6 +780,8 @@ void QgsSymbolV2::renderFeature( const QgsFeature& feature, QgsRenderContext& co

for ( int i = 0; i < mp->numGeometries(); ++i )
{
mSymbolRenderContext->expressionContextScope()->setVariable( "geometry_part_num", i + 1 );

const QgsPointV2* point = static_cast< const QgsPointV2* >( mp->geometryN( i ) );
_getPoint( pt, context, point );
static_cast<QgsMarkerSymbolV2*>( this )->renderPoint( pt, &feature, context, layer, selected );
Expand Down Expand Up @@ -793,6 +809,8 @@ void QgsSymbolV2::renderFeature( const QgsFeature& feature, QgsRenderContext& co

for ( unsigned int i = 0; i < num; ++i )
{
mSymbolRenderContext->expressionContextScope()->setVariable( "geometry_part_num", i + 1 );

if ( geomCollection )
{
context.setGeometry( geomCollection->geometryN( i ) );
Expand Down Expand Up @@ -824,6 +842,8 @@ void QgsSymbolV2::renderFeature( const QgsFeature& feature, QgsRenderContext& co

for ( unsigned int i = 0; i < num; ++i )
{
mSymbolRenderContext->expressionContextScope()->setVariable( "geometry_part_num", i + 1 );

if ( geomCollection )
{
context.setGeometry( geomCollection->geometryN( i ) );
Expand Down Expand Up @@ -869,13 +889,21 @@ void QgsSymbolV2::renderFeature( const QgsFeature& feature, QgsRenderContext& co
{
delete segmentizedGeometry;
}

context.expressionContext().popScope();
}

QgsSymbolV2RenderContext* QgsSymbolV2::symbolRenderContext()
{
return mSymbolRenderContext;
}

////////////////////


QgsSymbolV2RenderContext::QgsSymbolV2RenderContext( QgsRenderContext& c, QgsSymbolV2::OutputUnit u, qreal alpha, bool selected, int renderHints, const QgsFeature* f, const QgsFields* fields, const QgsMapUnitScale& mapUnitScale )
: mRenderContext( c ),
mExpressionContextScope( nullptr ),
mOutputUnit( u ),
mMapUnitScale( mapUnitScale ),
mAlpha( alpha ),
Expand All @@ -888,7 +916,7 @@ QgsSymbolV2RenderContext::QgsSymbolV2RenderContext( QgsRenderContext& c, QgsSymb

QgsSymbolV2RenderContext::~QgsSymbolV2RenderContext()
{

delete mExpressionContextScope;
}

void QgsSymbolV2RenderContext::setOriginalValueVariable( const QVariant& value )
Expand Down Expand Up @@ -916,6 +944,16 @@ QgsSymbolV2RenderContext& QgsSymbolV2RenderContext::operator=( const QgsSymbolV2
return *this;
}

QgsExpressionContextScope* QgsSymbolV2RenderContext::expressionContextScope()
{
return mExpressionContextScope;
}

void QgsSymbolV2RenderContext::setExpressionContextScope( QgsExpressionContextScope* contextScope )
{
mExpressionContextScope = contextScope;
}

///////////////////

QgsMarkerSymbolV2* QgsMarkerSymbolV2::createSimple( const QgsStringMap& properties )
Expand Down
26 changes: 26 additions & 0 deletions src/core/symbology-ng/qgssymbolv2.h
Expand Up @@ -241,6 +241,13 @@ class CORE_EXPORT QgsSymbolV2
*/
void renderFeature( const QgsFeature& feature, QgsRenderContext& context, int layer = -1, bool selected = false, bool drawVertexMarker = false, int currentVertexMarkerType = 0, int currentVertexMarkerSize = 0 );

/**
* Returns the symbol render context. Only valid between startRender and stopRender calls.
*
* @return The symbol render context
*/
QgsSymbolV2RenderContext* symbolRenderContext();

protected:
QgsSymbolV2( SymbolType type, const QgsSymbolLayerV2List& layers ); // can't be instantiated

Expand Down Expand Up @@ -310,6 +317,9 @@ class CORE_EXPORT QgsSymbolV2
const QgsVectorLayer* mLayer; //current vectorlayer

private:
//! Initialized in startRender, destroyed in stopRender
QgsSymbolV2RenderContext* mSymbolRenderContext;

Q_DISABLE_COPY( QgsSymbolV2 )

};
Expand Down Expand Up @@ -365,8 +375,24 @@ class CORE_EXPORT QgsSymbolV2RenderContext
// workaround for sip 4.7. Don't use assignment - will fail with assertion error
QgsSymbolV2RenderContext& operator=( const QgsSymbolV2RenderContext& );

/**
* This scope is always available when a symbol of this type is being rendered.
*
* @return An expression scope for details about this symbol
*/
QgsExpressionContextScope* expressionContextScope();
/**
* Set an expression scope for this symbol.
*
* Will take ownership.
*
* @param contextScope An expression scope for details about this symbol
*/
void setExpressionContextScope( QgsExpressionContextScope* contextScope );

private:
QgsRenderContext& mRenderContext;
QgsExpressionContextScope* mExpressionContextScope;
QgsSymbolV2::OutputUnit mOutputUnit;
QgsMapUnitScale mMapUnitScale;
qreal mAlpha;
Expand Down
8 changes: 8 additions & 0 deletions tests/src/core/testqgsexpressioncontext.cpp
Expand Up @@ -219,6 +219,9 @@ void TestQgsExpressionContext::contextScopeFunctions()
void TestQgsExpressionContext::contextStack()
{
QgsExpressionContext context;

context.popScope();

//test retrieving from empty context
QVERIFY( !context.hasVariable( "test" ) );
QVERIFY( !context.variable( "test" ).isValid() );
Expand Down Expand Up @@ -290,6 +293,11 @@ void TestQgsExpressionContext::contextStack()
scope2->addVariable( QgsExpressionContextScope::StaticVariable( "readonly", 5, true ) );
QVERIFY( context.isReadOnly( "readonly" ) );
QVERIFY( !context.isReadOnly( "test" ) );

// Check scopes can be popped
context.popScope();
QCOMPARE( scopes.length(), 2 );
QCOMPARE( scopes.at( 0 ), scope1 );
}

void TestQgsExpressionContext::contextCopy()
Expand Down
1 change: 1 addition & 0 deletions tests/src/python/CMakeLists.txt
Expand Up @@ -59,6 +59,7 @@ ADD_PYTHON_TEST(PyQgsShapefileProvider test_provider_shapefile.py)
ADD_PYTHON_TEST(PyQgsSpatialIndex test_qgsspatialindex.py)
ADD_PYTHON_TEST(PyQgsSpatialiteProvider test_provider_spatialite.py)
ADD_PYTHON_TEST(PyQgsSymbolLayerV2 test_qgssymbollayerv2.py)
ADD_PYTHON_TEST(PyQgsSymbolExpressionVariables test_qgssymbolexpressionvariables.py)
ADD_PYTHON_TEST(PyQgsSyntacticSugar test_syntactic_sugar.py)
ADD_PYTHON_TEST(PyQgsVectorColorRamp test_qgsvectorcolorramp.py)
ADD_PYTHON_TEST(PyQgsVectorFileWriter test_qgsvectorfilewriter.py)
Expand Down

0 comments on commit 4e9afce

Please sign in to comment.