Skip to content

Commit

Permalink
Fix filtering legend content by map when renderer contains
Browse files Browse the repository at this point in the history
duplicate symbols (fix #14131)

Now, we don't test for map content using the symbols but instead
use the legend key during the hit test
  • Loading branch information
nyalldawson committed Jan 25, 2016
1 parent cf3c55b commit 39e1f68
Show file tree
Hide file tree
Showing 23 changed files with 278 additions and 14 deletions.
12 changes: 11 additions & 1 deletion python/core/qgsmaphittest.sip
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,18 @@ class QgsMapHitTest
* @param symbol symbol to find
* @param layer vector layer
* @note added in QGIS 2.12
* @see legendKeyVisible()
*/
bool symbolVisible( QgsSymbolV2* symbol, QgsVectorLayer* layer ) const;

/** Tests whether a given legend key is visible for a specified layer.
* @param ruleKey legend rule key
* @param layer vector layer
* @note added in QGIS 2.14
* @see symbolVisible()
*/
bool legendKeyVisible( const QString& ruleKey, QgsVectorLayer* layer ) const;

protected:

//! @note not available in Python bindings
Expand All @@ -43,11 +52,12 @@ class QgsMapHitTest
/** Runs test for visible symbols within a layer
* @param vl vector layer
* @param usedSymbols set for storage of visible symbols
* @param usedSymbolsRuleKey set of storage of visible legend rule keys
* @param context render context
* @note added in QGIS 2.12
* @note not available in Python bindings
*/
//void runHitTestLayer( QgsVectorLayer* vl, SymbolV2Set& usedSymbols, QgsRenderContext& context );
//void runHitTestLayer( QgsVectorLayer* vl, SymbolV2Set& usedSymbols, SymbolV2Set& usedSymbolsRuleKey, QgsRenderContext& context );


};
2 changes: 2 additions & 0 deletions python/core/symbology-ng/qgscategorizedsymbolrendererv2.sip
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ class QgsCategorizedSymbolRendererV2 : QgsFeatureRendererV2
//! @note added in 2.10
QgsLegendSymbolListV2 legendSymbolItemsV2() const;

virtual QSet< QString > legendKeysForFeature( QgsFeature& feature, QgsRenderContext& context );

QgsSymbolV2* sourceSymbol();
void setSourceSymbol( QgsSymbolV2* sym /Transfer/ );

Expand Down
7 changes: 7 additions & 0 deletions python/core/symbology-ng/qgsgraduatedsymbolrendererv2.sip
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,8 @@ class QgsGraduatedSymbolRendererV2 : QgsFeatureRendererV2
//! @note added in 2.10
QgsLegendSymbolListV2 legendSymbolItemsV2() const;

virtual QSet< QString > legendKeysForFeature( QgsFeature& feature, QgsRenderContext& context );

QgsSymbolV2* sourceSymbol();
void setSourceSymbol( QgsSymbolV2* sym /Transfer/ );

Expand Down Expand Up @@ -306,6 +308,11 @@ class QgsGraduatedSymbolRendererV2 : QgsFeatureRendererV2
protected:
QgsSymbolV2* symbolForValue( double value );

/** Returns the matching legend key for a value.
*/
QString legendKeyForValue( double value ) const;


private:
QgsGraduatedSymbolRendererV2( const QgsGraduatedSymbolRendererV2 & );
QgsGraduatedSymbolRendererV2 & operator=( const QgsGraduatedSymbolRendererV2 & );
Expand Down
6 changes: 6 additions & 0 deletions python/core/symbology-ng/qgsrendererv2.sip
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,12 @@ class QgsFeatureRendererV2
//TODO - QGIS 3.0 change PyName to originalSymbolForFeature when deprecated method is removed
virtual QgsSymbolV2* originalSymbolForFeature( QgsFeature& feature, QgsRenderContext& context ) /PyName=originalSymbolForFeature2/;

/**
* Return legend keys matching a specified feature.
* @note added in 2.14
*/
virtual QSet< QString > legendKeysForFeature( QgsFeature& feature, QgsRenderContext& context );

virtual void startRender( QgsRenderContext& context, const QgsFields& fields ) = 0;

//! @deprecated since 2.4 - not using QgsVectorLayer directly anymore
Expand Down
7 changes: 7 additions & 0 deletions python/core/symbology-ng/qgsrulebasedrendererv2.sip
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,11 @@ class QgsRuleBasedRendererV2 : QgsFeatureRendererV2
//! tell which symbols will be used to render the feature
QgsSymbolV2List symbolsForFeature( QgsFeature& feat, QgsRenderContext* context = 0 );

/** Returns which legend keys match the feature
* @note added in QGIS 2.14
*/
QSet< QString > legendKeysForFeature( QgsFeature& feat, QgsRenderContext* context = nullptr );

//! tell which rules will be used to render the feature
QList<QgsRuleBasedRendererV2::Rule*> rulesForFeature( QgsFeature& feat, QgsRenderContext* context = 0 );

Expand Down Expand Up @@ -388,6 +393,8 @@ class QgsRuleBasedRendererV2 : QgsFeatureRendererV2

virtual QgsSymbolV2List originalSymbolsForFeature( QgsFeature& feat, QgsRenderContext &context );

virtual QSet<QString> legendKeysForFeature( QgsFeature& feature, QgsRenderContext& context );

//! returns bitwise OR-ed capabilities of the renderer
virtual int capabilities();

Expand Down
2 changes: 2 additions & 0 deletions python/core/symbology-ng/qgssinglesymbolrendererv2.sip
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ class QgsSingleSymbolRendererV2 : QgsFeatureRendererV2
//! @note added in 2.6
virtual QgsLegendSymbolListV2 legendSymbolItemsV2() const;

virtual QSet< QString > legendKeysForFeature( QgsFeature& feature, QgsRenderContext& context );

//! creates a QgsSingleSymbolRendererV2 from an existing renderer.
//! @note added in 2.5
//! @returns a new renderer if the conversion was possible, otherwise 0.
Expand Down
7 changes: 4 additions & 3 deletions src/core/layertree/qgslayertreemodel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1058,12 +1058,13 @@ QList<QgsLayerTreeModelLegendNode*> QgsLayerTreeModel::filterLegendNodes( const
{
Q_FOREACH ( QgsLayerTreeModelLegendNode* node, nodes )
{
QgsSymbolV2* ruleKey = reinterpret_cast< QgsSymbolV2* >( node->data( QgsSymbolV2LegendNode::SymbolV2LegacyRuleKeyRole ).value<void*>() );
if ( ruleKey )
QgsSymbolV2* symbolKey = reinterpret_cast< QgsSymbolV2* >( node->data( QgsSymbolV2LegendNode::SymbolV2LegacyRuleKeyRole ).value<void*>() );
if ( symbolKey )
{
QString ruleKey = node->data( QgsSymbolV2LegendNode::RuleKeyRole ).toString();
if ( QgsVectorLayer* vl = qobject_cast<QgsVectorLayer*>( node->layerNode()->layer() ) )
{
if ( mLegendFilterHitTest->symbolVisible( ruleKey, vl ) )
if ( mLegendFilterHitTest->legendKeyVisible( ruleKey, vl ) )
filtered << node;
}
}
Expand Down
21 changes: 19 additions & 2 deletions src/core/qgsmaphittest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ void QgsMapHitTest::run()
if ( vl->hasScaleBasedVisibility() && ( mSettings.scale() < vl->minimumScale() || mSettings.scale() > vl->maximumScale() ) )
{
mHitTest[vl] = SymbolV2Set(); // no symbols -> will not be shown
mHitTestRuleKey[vl] = SymbolV2Set();
continue;
}

Expand All @@ -74,7 +75,8 @@ void QgsMapHitTest::run()

context.expressionContext() << QgsExpressionContextUtils::layerScope( vl );
SymbolV2Set& usedSymbols = mHitTest[vl];
runHitTestLayer( vl, usedSymbols, context );
SymbolV2Set& usedSymbolsRuleKey = mHitTestRuleKey[vl];
runHitTestLayer( vl, usedSymbols, usedSymbolsRuleKey, context );
}

painter.end();
Expand All @@ -88,7 +90,15 @@ bool QgsMapHitTest::symbolVisible( QgsSymbolV2* symbol, QgsVectorLayer* layer )
return mHitTest.value( layer ).contains( QgsSymbolLayerV2Utils::symbolProperties( symbol ) );
}

void QgsMapHitTest::runHitTestLayer( QgsVectorLayer* vl, SymbolV2Set& usedSymbols, QgsRenderContext& context )
bool QgsMapHitTest::legendKeyVisible( const QString& ruleKey, QgsVectorLayer* layer ) const
{
if ( !layer || !mHitTestRuleKey.contains( layer ) )
return false;

return mHitTestRuleKey.value( layer ).contains( ruleKey );
}

void QgsMapHitTest::runHitTestLayer( QgsVectorLayer* vl, SymbolV2Set& usedSymbols, SymbolV2Set& usedSymbolsRuleKey, QgsRenderContext& context )
{
bool hasStyleOverride = mSettings.layerStyleOverrides().contains( vl->id() );
if ( hasStyleOverride )
Expand Down Expand Up @@ -125,6 +135,7 @@ void QgsMapHitTest::runHitTestLayer( QgsVectorLayer* vl, SymbolV2Set& usedSymbol
QgsFeatureIterator fi = vl->getFeatures( request );

SymbolV2Set lUsedSymbols;
SymbolV2Set lUsedSymbolsRuleKey;
bool allExpressionFalse = false;
bool hasExpression = mLayerFilterExpression.contains( vl->id() );
QScopedPointer<QgsExpression> expr;
Expand Down Expand Up @@ -156,6 +167,11 @@ void QgsMapHitTest::runHitTestLayer( QgsVectorLayer* vl, SymbolV2Set& usedSymbol

//make sure we store string representation of symbol, not pointer
//otherwise layer style override changes will delete original symbols and leave hanging pointers
Q_FOREACH ( const QString& legendKey, r->legendKeysForFeature( f, context ) )
{
lUsedSymbolsRuleKey.insert( legendKey );
}

if ( moreSymbolsPerFeature )
{
Q_FOREACH ( QgsSymbolV2* s, r->originalSymbolsForFeature( f, context ) )
Expand All @@ -177,6 +193,7 @@ void QgsMapHitTest::runHitTestLayer( QgsVectorLayer* vl, SymbolV2Set& usedSymbol
{
// QSet is implicitly shared => constant time
usedSymbols = lUsedSymbols;
usedSymbolsRuleKey = lUsedSymbolsRuleKey;
}

if ( hasStyleOverride )
Expand Down
15 changes: 14 additions & 1 deletion src/core/qgsmaphittest.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,18 @@ class CORE_EXPORT QgsMapHitTest
* @param symbol symbol to find
* @param layer vector layer
* @note added in QGIS 2.12
* @see legendKeyVisible()
*/
bool symbolVisible( QgsSymbolV2* symbol, QgsVectorLayer* layer ) const;

/** Tests whether a given legend key is visible for a specified layer.
* @param ruleKey legend rule key
* @param layer vector layer
* @note added in QGIS 2.14
* @see symbolVisible()
*/
bool legendKeyVisible( const QString& ruleKey, QgsVectorLayer* layer ) const;

protected:

//! @note not available in Python bindings
Expand All @@ -66,18 +75,22 @@ class CORE_EXPORT QgsMapHitTest
/** Runs test for visible symbols within a layer
* @param vl vector layer
* @param usedSymbols set for storage of visible symbols
* @param usedSymbolsRuleKey set of storage of visible legend rule keys
* @param context render context
* @note added in QGIS 2.12
* @note not available in Python bindings
*/
void runHitTestLayer( QgsVectorLayer* vl, SymbolV2Set& usedSymbols, QgsRenderContext& context );
void runHitTestLayer( QgsVectorLayer* vl, SymbolV2Set& usedSymbols, SymbolV2Set& usedSymbolsRuleKey, QgsRenderContext& context );

//! The initial map settings
QgsMapSettings mSettings;

//! The hit test
HitTest mHitTest;

//! The hit test, using legend rule keys
HitTest mHitTestRuleKey;

//! List of expression filter for each layer
LayerFilterExpression mLayerFilterExpression;

Expand Down
30 changes: 28 additions & 2 deletions src/core/symbology-ng/qgscategorizedsymbolrendererv2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -245,9 +245,8 @@ QgsSymbolV2* QgsCategorizedSymbolRendererV2::symbolForFeature( QgsFeature& featu
}


QgsSymbolV2* QgsCategorizedSymbolRendererV2::originalSymbolForFeature( QgsFeature& feature, QgsRenderContext &context )
QVariant QgsCategorizedSymbolRendererV2::valueForFeature( QgsFeature& feature, QgsRenderContext &context ) const
{
Q_UNUSED( context );
QgsAttributes attrs = feature.attributes();
QVariant value;
if ( mAttrNum == -1 )
Expand All @@ -261,6 +260,13 @@ QgsSymbolV2* QgsCategorizedSymbolRendererV2::originalSymbolForFeature( QgsFeatur
value = attrs.value( mAttrNum );
}

return value;
}

QgsSymbolV2* QgsCategorizedSymbolRendererV2::originalSymbolForFeature( QgsFeature& feature, QgsRenderContext &context )
{
QVariant value = valueForFeature( feature, context );

// find the right symbol for the category
QgsSymbolV2 *symbol = symbolForValue( value );
if ( symbol == skipRender() )
Expand Down Expand Up @@ -859,6 +865,26 @@ QgsLegendSymbolListV2 QgsCategorizedSymbolRendererV2::legendSymbolItemsV2() cons
return QgsFeatureRendererV2::legendSymbolItemsV2();
}

QSet<QString> QgsCategorizedSymbolRendererV2::legendKeysForFeature( QgsFeature& feature, QgsRenderContext& context )
{
QString value = valueForFeature( feature, context ).toString();
int i = 0;

Q_FOREACH ( const QgsRendererCategoryV2& cat, mCategories )
{
if ( value == cat.value() )
{
if ( cat.renderState() )
return QSet< QString >() << QString::number( i );
else
return QSet< QString >();
}
i++;
}

return QSet< QString >();
}

QgsSymbolV2* QgsCategorizedSymbolRendererV2::sourceSymbol()
{
return mSourceSymbol.data();
Expand Down
8 changes: 8 additions & 0 deletions src/core/symbology-ng/qgscategorizedsymbolrendererv2.h
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,8 @@ class CORE_EXPORT QgsCategorizedSymbolRendererV2 : public QgsFeatureRendererV2
//! @note added in 2.10
QgsLegendSymbolListV2 legendSymbolItemsV2() const override;

virtual QSet< QString > legendKeysForFeature( QgsFeature& feature, QgsRenderContext& context ) override;

QgsSymbolV2* sourceSymbol();
void setSourceSymbol( QgsSymbolV2* sym );

Expand Down Expand Up @@ -229,6 +231,12 @@ class CORE_EXPORT QgsCategorizedSymbolRendererV2 : public QgsFeatureRendererV2
QgsSymbolV2* skipRender();

QgsSymbolV2* symbolForValue( const QVariant& value );

private:

/** Returns calculated classification value for a feature */
QVariant valueForFeature( QgsFeature& feature, QgsRenderContext &context ) const;

};
Q_NOWARN_DEPRECATED_POP

Expand Down
44 changes: 42 additions & 2 deletions src/core/symbology-ng/qgsgraduatedsymbolrendererv2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,24 @@ QgsSymbolV2* QgsGraduatedSymbolRendererV2::symbolForValue( double value )
return nullptr;
}

QString QgsGraduatedSymbolRendererV2::legendKeyForValue( double value ) const
{
int i = 0;
Q_FOREACH ( const QgsRendererRangeV2& range, mRanges )
{
if ( range.lowerValue() <= value && range.upperValue() >= value )
{
if ( range.renderState() || mCounting )
return QString::number( i );
else
return QString::null;
}
i++;
}
// the value is out of the range: return NULL
return QString::null;
}

QgsSymbolV2* QgsGraduatedSymbolRendererV2::symbolForFeature( QgsFeature& feature, QgsRenderContext &context )
{
QgsSymbolV2* symbol = originalSymbolForFeature( feature, context );
Expand Down Expand Up @@ -357,9 +375,8 @@ QgsSymbolV2* QgsGraduatedSymbolRendererV2::symbolForFeature( QgsFeature& feature
return tempSymbol;
}

QgsSymbolV2* QgsGraduatedSymbolRendererV2::originalSymbolForFeature( QgsFeature& feature, QgsRenderContext &context )
QVariant QgsGraduatedSymbolRendererV2::valueForFeature( QgsFeature& feature, QgsRenderContext &context ) const
{
Q_UNUSED( context );
QgsAttributes attrs = feature.attributes();
QVariant value;
if ( mAttrNum < 0 || mAttrNum >= attrs.count() )
Expand All @@ -371,6 +388,13 @@ QgsSymbolV2* QgsGraduatedSymbolRendererV2::originalSymbolForFeature( QgsFeature&
value = attrs.at( mAttrNum );
}

return value;
}

QgsSymbolV2* QgsGraduatedSymbolRendererV2::originalSymbolForFeature( QgsFeature& feature, QgsRenderContext &context )
{
QVariant value = valueForFeature( feature, context );

// Null values should not be categorized
if ( value.isNull() )
return nullptr;
Expand Down Expand Up @@ -1245,6 +1269,22 @@ QgsLegendSymbolListV2 QgsGraduatedSymbolRendererV2::legendSymbolItemsV2() const
return QgsFeatureRendererV2::legendSymbolItemsV2();
}

QSet< QString > QgsGraduatedSymbolRendererV2::legendKeysForFeature( QgsFeature& feature, QgsRenderContext& context )
{
QVariant value = valueForFeature( feature, context );

// Null values should not be categorized
if ( value.isNull() )
return QSet< QString >();

// find the right category
QString key = legendKeyForValue( value.toDouble() );
if ( !key.isNull() )
return QSet< QString >() << key;
else
return QSet< QString >();
}

QgsLegendSymbolList QgsGraduatedSymbolRendererV2::legendSymbolItems( double scaleDenominator, const QString& rule )
{
Q_UNUSED( scaleDenominator );
Expand Down
Loading

0 comments on commit 39e1f68

Please sign in to comment.