Skip to content

Commit 4e9afce

Browse files
committedJan 13, 2016
Expression variable for the currently rendered part
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
1 parent 3da051b commit 4e9afce

File tree

12 files changed

+228
-9
lines changed

12 files changed

+228
-9
lines changed
 

‎python/core/qgsexpressioncontext.sip

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,11 @@ class QgsExpressionContext
344344
*/
345345
void appendScope( QgsExpressionContextScope* scope /Transfer/ );
346346

347+
/**
348+
* Remove the last scope from the expression context.
349+
*/
350+
void popScope();
351+
347352
/** Appends a scope to the end of the context. This scope will override
348353
* any matching variables or functions provided by existing scopes within the
349354
* context. Ownership of the scope is transferred to the stack.

‎python/core/symbology-ng/qgssymbolv2.sip

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,13 @@ class QgsSymbolV2
191191
*/
192192
void renderFeature( const QgsFeature& feature, QgsRenderContext& context, int layer = -1, bool selected = false, bool drawVertexMarker = false, int currentVertexMarkerType = 0, int currentVertexMarkerSize = 0 );
193193

194+
/**
195+
* Returns the symbol render context. Only valid between startRender and stopRender calls.
196+
*
197+
* @return The symbol render context
198+
*/
199+
QgsSymbolV2RenderContext* symbolRenderContext();
200+
194201
protected:
195202
QgsSymbolV2( SymbolType type, const QgsSymbolLayerV2List& layers /Transfer/ ); // can't be instantiated
196203

@@ -283,6 +290,21 @@ class QgsSymbolV2RenderContext
283290

284291
// workaround for sip 4.7. Don't use assignment - will fail with assertion error
285292
// QgsSymbolV2RenderContext& operator=( const QgsSymbolV2RenderContext& );
293+
294+
/**
295+
* This scope is always available when a symbol of this type is being rendered.
296+
*
297+
* @return An expression scope for details about this symbol
298+
*/
299+
QgsExpressionContextScope* expressionContextScope();
300+
/**
301+
* Set an expression scope for this symbol.
302+
*
303+
* Will take ownership.
304+
*
305+
* @param contextScope An expression scope for details about this symbol
306+
*/
307+
void setExpressionContextScope( QgsExpressionContextScope* contextScope /Transfer/);
286308
};
287309

288310

‎src/core/qgsexpressioncontext.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,12 @@ void QgsExpressionContext::appendScope( QgsExpressionContextScope* scope )
381381
mStack.append( scope );
382382
}
383383

384+
void QgsExpressionContext::popScope()
385+
{
386+
if ( !mStack.isEmpty() )
387+
mStack.pop_back();
388+
}
389+
384390
QgsExpressionContext& QgsExpressionContext::operator<<( QgsExpressionContextScope* scope )
385391
{
386392
mStack.append( scope );

‎src/core/qgsexpressioncontext.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,11 @@ class CORE_EXPORT QgsExpressionContext
378378
*/
379379
void appendScope( QgsExpressionContextScope* scope );
380380

381+
/**
382+
* Remove the last scope from the expression context.
383+
*/
384+
void popScope();
385+
381386
/** Appends a scope to the end of the context. This scope will override
382387
* any matching variables or functions provided by existing scopes within the
383388
* context. Ownership of the scope is transferred to the stack.

‎src/core/symbology-ng/qgsgeometrygeneratorsymbollayerv2.cpp

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -186,17 +186,20 @@ bool QgsGeometryGeneratorSymbolLayerV2::isCompatibleWithSymbol( QgsSymbolV2* sym
186186
Q_UNUSED( symbol )
187187
return true;
188188
}
189-
190189
void QgsGeometryGeneratorSymbolLayerV2::render( QgsSymbolV2RenderContext& context )
191190
{
192-
QgsGeometry geom = mExpression->evaluate( &context.renderContext().expressionContext() ).value<QgsGeometry>();
193191
if ( context.feature() )
194192
{
195-
QgsFeature f = *context.feature();
193+
QgsExpressionContext& expressionContext = context.renderContext().expressionContext();
194+
195+
QgsFeature f = expressionContext.feature();
196+
QgsGeometry geom = mExpression->evaluate( &expressionContext ).value<QgsGeometry>();
196197
f.setGeometry( geom );
197198

199+
QgsExpressionContextScope* subSymbolExpressionContextScope = mSymbol->symbolRenderContext()->expressionContextScope();
200+
201+
subSymbolExpressionContextScope->setFeature( f );
202+
198203
mSymbol->renderFeature( f, context.renderContext() );
199204
}
200205
}
201-
202-

‎src/core/symbology-ng/qgssymbolv2.cpp

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ QgsSymbolV2::QgsSymbolV2( SymbolType type, const QgsSymbolLayerV2List& layers )
8888
, mRenderHints( 0 )
8989
, mClipFeaturesToExtent( true )
9090
, mLayer( nullptr )
91+
, mSymbolRenderContext( nullptr )
9192
{
9293

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

236237
QgsSymbolV2::~QgsSymbolV2()
237238
{
239+
delete mSymbolRenderContext;
238240
// delete all symbol layers (we own them, so it's okay)
239241
qDeleteAll( mLayers );
240242
}
@@ -434,19 +436,27 @@ bool QgsSymbolV2::changeSymbolLayer( int index, QgsSymbolLayerV2* layer )
434436

435437
void QgsSymbolV2::startRender( QgsRenderContext& context, const QgsFields* fields )
436438
{
439+
delete mSymbolRenderContext;
440+
mSymbolRenderContext = new QgsSymbolV2RenderContext( context, outputUnit(), mAlpha, false, mRenderHints, nullptr, fields, mapUnitScale() );
441+
437442
QgsSymbolV2RenderContext symbolContext( context, outputUnit(), mAlpha, false, mRenderHints, nullptr, fields, mapUnitScale() );
438443

444+
QgsExpressionContextScope* scope = new QgsExpressionContextScope( QApplication::translate( "QgsSymbolV2", "Symbol Scope" ) );
445+
446+
mSymbolRenderContext->setExpressionContextScope( scope );
439447

440448
Q_FOREACH ( QgsSymbolLayerV2* layer, mLayers )
441449
layer->startRender( symbolContext );
442450
}
443451

444452
void QgsSymbolV2::stopRender( QgsRenderContext& context )
445453
{
446-
QgsSymbolV2RenderContext symbolContext( context, outputUnit(), mAlpha, false, mRenderHints, nullptr, nullptr, mapUnitScale() );
447-
454+
Q_UNUSED( context )
448455
Q_FOREACH ( QgsSymbolLayerV2* layer, mLayers )
449-
layer->stopRender( symbolContext );
456+
layer->stopRender( *mSymbolRenderContext );
457+
458+
delete mSymbolRenderContext;
459+
mSymbolRenderContext = nullptr;
450460

451461
mLayer = nullptr;
452462
}
@@ -701,6 +711,10 @@ void QgsSymbolV2::renderFeature( const QgsFeature& feature, QgsRenderContext& co
701711
deleteSegmentizedGeometry = true;
702712
}
703713

714+
context.expressionContext().appendScope( mSymbolRenderContext->expressionContextScope() );
715+
mSymbolRenderContext->expressionContextScope()->setVariable( "geometry_part_count", segmentizedGeometry->geometry()->partCount() );
716+
mSymbolRenderContext->expressionContextScope()->setVariable( "geometry_part_num", 1 );
717+
704718
switch ( QgsWKBTypes::flatType( segmentizedGeometry->geometry()->wkbType() ) )
705719
{
706720
case QgsWKBTypes::Point:
@@ -766,6 +780,8 @@ void QgsSymbolV2::renderFeature( const QgsFeature& feature, QgsRenderContext& co
766780

767781
for ( int i = 0; i < mp->numGeometries(); ++i )
768782
{
783+
mSymbolRenderContext->expressionContextScope()->setVariable( "geometry_part_num", i + 1 );
784+
769785
const QgsPointV2* point = static_cast< const QgsPointV2* >( mp->geometryN( i ) );
770786
_getPoint( pt, context, point );
771787
static_cast<QgsMarkerSymbolV2*>( this )->renderPoint( pt, &feature, context, layer, selected );
@@ -793,6 +809,8 @@ void QgsSymbolV2::renderFeature( const QgsFeature& feature, QgsRenderContext& co
793809

794810
for ( unsigned int i = 0; i < num; ++i )
795811
{
812+
mSymbolRenderContext->expressionContextScope()->setVariable( "geometry_part_num", i + 1 );
813+
796814
if ( geomCollection )
797815
{
798816
context.setGeometry( geomCollection->geometryN( i ) );
@@ -824,6 +842,8 @@ void QgsSymbolV2::renderFeature( const QgsFeature& feature, QgsRenderContext& co
824842

825843
for ( unsigned int i = 0; i < num; ++i )
826844
{
845+
mSymbolRenderContext->expressionContextScope()->setVariable( "geometry_part_num", i + 1 );
846+
827847
if ( geomCollection )
828848
{
829849
context.setGeometry( geomCollection->geometryN( i ) );
@@ -869,13 +889,21 @@ void QgsSymbolV2::renderFeature( const QgsFeature& feature, QgsRenderContext& co
869889
{
870890
delete segmentizedGeometry;
871891
}
892+
893+
context.expressionContext().popScope();
894+
}
895+
896+
QgsSymbolV2RenderContext* QgsSymbolV2::symbolRenderContext()
897+
{
898+
return mSymbolRenderContext;
872899
}
873900

874901
////////////////////
875902

876903

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

889917
QgsSymbolV2RenderContext::~QgsSymbolV2RenderContext()
890918
{
891-
919+
delete mExpressionContextScope;
892920
}
893921

894922
void QgsSymbolV2RenderContext::setOriginalValueVariable( const QVariant& value )
@@ -916,6 +944,16 @@ QgsSymbolV2RenderContext& QgsSymbolV2RenderContext::operator=( const QgsSymbolV2
916944
return *this;
917945
}
918946

947+
QgsExpressionContextScope* QgsSymbolV2RenderContext::expressionContextScope()
948+
{
949+
return mExpressionContextScope;
950+
}
951+
952+
void QgsSymbolV2RenderContext::setExpressionContextScope( QgsExpressionContextScope* contextScope )
953+
{
954+
mExpressionContextScope = contextScope;
955+
}
956+
919957
///////////////////
920958

921959
QgsMarkerSymbolV2* QgsMarkerSymbolV2::createSimple( const QgsStringMap& properties )

‎src/core/symbology-ng/qgssymbolv2.h

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,13 @@ class CORE_EXPORT QgsSymbolV2
241241
*/
242242
void renderFeature( const QgsFeature& feature, QgsRenderContext& context, int layer = -1, bool selected = false, bool drawVertexMarker = false, int currentVertexMarkerType = 0, int currentVertexMarkerSize = 0 );
243243

244+
/**
245+
* Returns the symbol render context. Only valid between startRender and stopRender calls.
246+
*
247+
* @return The symbol render context
248+
*/
249+
QgsSymbolV2RenderContext* symbolRenderContext();
250+
244251
protected:
245252
QgsSymbolV2( SymbolType type, const QgsSymbolLayerV2List& layers ); // can't be instantiated
246253

@@ -310,6 +317,9 @@ class CORE_EXPORT QgsSymbolV2
310317
const QgsVectorLayer* mLayer; //current vectorlayer
311318

312319
private:
320+
//! Initialized in startRender, destroyed in stopRender
321+
QgsSymbolV2RenderContext* mSymbolRenderContext;
322+
313323
Q_DISABLE_COPY( QgsSymbolV2 )
314324

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

378+
/**
379+
* This scope is always available when a symbol of this type is being rendered.
380+
*
381+
* @return An expression scope for details about this symbol
382+
*/
383+
QgsExpressionContextScope* expressionContextScope();
384+
/**
385+
* Set an expression scope for this symbol.
386+
*
387+
* Will take ownership.
388+
*
389+
* @param contextScope An expression scope for details about this symbol
390+
*/
391+
void setExpressionContextScope( QgsExpressionContextScope* contextScope );
392+
368393
private:
369394
QgsRenderContext& mRenderContext;
395+
QgsExpressionContextScope* mExpressionContextScope;
370396
QgsSymbolV2::OutputUnit mOutputUnit;
371397
QgsMapUnitScale mMapUnitScale;
372398
qreal mAlpha;

‎tests/src/core/testqgsexpressioncontext.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,9 @@ void TestQgsExpressionContext::contextScopeFunctions()
219219
void TestQgsExpressionContext::contextStack()
220220
{
221221
QgsExpressionContext context;
222+
223+
context.popScope();
224+
222225
//test retrieving from empty context
223226
QVERIFY( !context.hasVariable( "test" ) );
224227
QVERIFY( !context.variable( "test" ).isValid() );
@@ -290,6 +293,11 @@ void TestQgsExpressionContext::contextStack()
290293
scope2->addVariable( QgsExpressionContextScope::StaticVariable( "readonly", 5, true ) );
291294
QVERIFY( context.isReadOnly( "readonly" ) );
292295
QVERIFY( !context.isReadOnly( "test" ) );
296+
297+
// Check scopes can be popped
298+
context.popScope();
299+
QCOMPARE( scopes.length(), 2 );
300+
QCOMPARE( scopes.at( 0 ), scope1 );
293301
}
294302

295303
void TestQgsExpressionContext::contextCopy()

‎tests/src/python/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ ADD_PYTHON_TEST(PyQgsShapefileProvider test_provider_shapefile.py)
5959
ADD_PYTHON_TEST(PyQgsSpatialIndex test_qgsspatialindex.py)
6060
ADD_PYTHON_TEST(PyQgsSpatialiteProvider test_provider_spatialite.py)
6161
ADD_PYTHON_TEST(PyQgsSymbolLayerV2 test_qgssymbollayerv2.py)
62+
ADD_PYTHON_TEST(PyQgsSymbolExpressionVariables test_qgssymbolexpressionvariables.py)
6263
ADD_PYTHON_TEST(PyQgsSyntacticSugar test_syntactic_sugar.py)
6364
ADD_PYTHON_TEST(PyQgsVectorColorRamp test_qgsvectorcolorramp.py)
6465
ADD_PYTHON_TEST(PyQgsVectorFileWriter test_qgsvectorfilewriter.py)
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# -*- coding: utf-8 -*-
2+
3+
"""
4+
***************************************************************************
5+
test_qgssymbolexpressionvariables.py
6+
---------------------
7+
Date : January 2016
8+
Copyright : (C) 2016 by Matthias Kuhn
9+
Email : matthias at opengis dot ch
10+
***************************************************************************
11+
* *
12+
* This program is free software; you can redistribute it and/or modify *
13+
* it under the terms of the GNU General Public License as published by *
14+
* the Free Software Foundation; either version 2 of the License, or *
15+
* (at your option) any later version. *
16+
* *
17+
***************************************************************************
18+
"""
19+
20+
__author__ = 'Matthias Kuhn'
21+
__date__ = 'January 2016'
22+
__copyright__ = '(C) 2016, Matthiasd Kuhn'
23+
# This will get replaced with a git SHA1 when you do a git archive
24+
__revision__ = '$Format:%H$'
25+
26+
import qgis
27+
import os
28+
29+
from PyQt4.QtCore import QSize
30+
31+
from qgis.core import (QgsVectorLayer,
32+
QgsMapLayerRegistry,
33+
QgsRectangle,
34+
QgsMultiRenderChecker,
35+
QgsSingleSymbolRendererV2,
36+
QgsFillSymbolV2,
37+
QgsMarkerSymbolV2,
38+
QgsRendererCategoryV2,
39+
QgsCategorizedSymbolRendererV2,
40+
QgsGraduatedSymbolRendererV2,
41+
QgsRendererRangeV2,
42+
QgsFeatureRequest
43+
)
44+
from utilities import (unitTestDataPath,
45+
getQgisTestApp,
46+
TestCase,
47+
unittest
48+
)
49+
# Convenience instances in case you may need them
50+
# not used in this test
51+
QGISAPP, CANVAS, IFACE, PARENT = getQgisTestApp()
52+
TEST_DATA_DIR = unitTestDataPath()
53+
54+
55+
class TestQgsSymbolExpressionVariables(TestCase):
56+
57+
def setUp(self):
58+
myShpFile = os.path.join(TEST_DATA_DIR, 'polys.shp')
59+
self.layer = QgsVectorLayer(myShpFile, 'Polys', 'ogr')
60+
QgsMapLayerRegistry.instance().addMapLayer(self.layer)
61+
62+
rendered_layers = [self.layer.id()]
63+
self.mapsettings = CANVAS.mapSettings()
64+
self.mapsettings.setOutputSize(QSize(400, 400))
65+
self.mapsettings.setOutputDpi(96)
66+
self.mapsettings.setExtent(QgsRectangle(-163, 22, -70, 52))
67+
self.mapsettings.setLayers(rendered_layers)
68+
69+
def tearDown(self):
70+
QgsMapLayerRegistry.instance().removeAllMapLayers()
71+
72+
def testPartNum(self):
73+
# Create rulebased style
74+
sym1 = QgsFillSymbolV2.createSimple({'color': '#fdbf6f'})
75+
76+
renderer = QgsSingleSymbolRendererV2(sym1)
77+
renderer.symbols()[0].symbolLayers()[0].setDataDefinedProperty('color', 'color_rgb( (@geometry_part_num - 1) * 200, 0, 0 )')
78+
self.layer.setRendererV2(renderer)
79+
80+
# Setup rendering check
81+
renderchecker = QgsMultiRenderChecker()
82+
renderchecker.setMapSettings(self.mapsettings)
83+
renderchecker.setControlName('expected_geometry_part_num')
84+
result = renderchecker.runTest('part_geometry_part_num')
85+
86+
self.assertTrue(result)
87+
88+
def testPartCount(self):
89+
# Create rulebased style
90+
sym1 = QgsFillSymbolV2.createSimple({'color': '#fdbf6f'})
91+
92+
renderer = QgsSingleSymbolRendererV2(sym1)
93+
renderer.symbols()[0].symbolLayers()[0].setDataDefinedProperty('color', 'color_rgb( (@geometry_part_count - 1) * 200, 0, 0 )')
94+
self.layer.setRendererV2(renderer)
95+
96+
# Setup rendering check
97+
renderchecker = QgsMultiRenderChecker()
98+
renderchecker.setMapSettings(self.mapsettings)
99+
renderchecker.setControlName('expected_geometry_part_count')
100+
result = renderchecker.runTest('part_geometry_part_count')
101+
102+
self.assertTrue(result)
103+
104+
if __name__ == '__main__':
105+
unittest.main()
Loading
Loading

0 commit comments

Comments
 (0)
Please sign in to comment.