QGIS/src/core/symbology-ng/qgscategorizedsymbolrenderer.cpp
Nyall Dawson 516bdfeed9 Fix build
2017-02-14 13:48:11 +10:00

966 lines
29 KiB
C++

/***************************************************************************
qgscategorizedsymbolrenderer.cpp
---------------------
begin : November 2009
copyright : (C) 2009 by Martin Dobias
email : wonder dot sk at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include <algorithm>
#include "qgscategorizedsymbolrenderer.h"
#include "qgssymbol.h"
#include "qgssymbollayerutils.h"
#include "qgscolorramp.h"
#include "qgspointdisplacementrenderer.h"
#include "qgsinvertedpolygonrenderer.h"
#include "qgspainteffect.h"
#include "qgspainteffectregistry.h"
#include "qgsscaleexpression.h"
#include "qgssymbollayer.h"
#include "qgsfeature.h"
#include "qgsvectorlayer.h"
#include "qgslogger.h"
#include "qgsproperty.h"
#include <QDomDocument>
#include <QDomElement>
#include <QSettings> // for legend
QgsRendererCategory::QgsRendererCategory()
: mRender( true )
{
}
QgsRendererCategory::QgsRendererCategory( const QVariant& value, QgsSymbol* symbol, const QString& label, bool render )
: mValue( value )
, mSymbol( symbol )
, mLabel( label )
, mRender( render )
{
}
QgsRendererCategory::QgsRendererCategory( const QgsRendererCategory& cat )
: mValue( cat.mValue )
, mSymbol( cat.mSymbol ? cat.mSymbol->clone() : nullptr )
, mLabel( cat.mLabel )
, mRender( cat.mRender )
{
}
// copy+swap idion, the copy is done through the 'pass by value'
QgsRendererCategory& QgsRendererCategory::operator=( QgsRendererCategory cat )
{
swap( cat );
return *this;
}
void QgsRendererCategory::swap( QgsRendererCategory & cat )
{
std::swap( mValue, cat.mValue );
std::swap( mSymbol, cat.mSymbol );
std::swap( mLabel, cat.mLabel );
}
QVariant QgsRendererCategory::value() const
{
return mValue;
}
QgsSymbol* QgsRendererCategory::symbol() const
{
return mSymbol.get();
}
QString QgsRendererCategory::label() const
{
return mLabel;
}
bool QgsRendererCategory::renderState() const
{
return mRender;
}
void QgsRendererCategory::setValue( const QVariant &value )
{
mValue = value;
}
void QgsRendererCategory::setSymbol( QgsSymbol* s )
{
if ( mSymbol.get() != s ) mSymbol.reset( s );
}
void QgsRendererCategory::setLabel( const QString &label )
{
mLabel = label;
}
void QgsRendererCategory::setRenderState( bool render )
{
mRender = render;
}
QString QgsRendererCategory::dump() const
{
return QStringLiteral( "%1::%2::%3:%4\n" ).arg( mValue.toString(), mLabel, mSymbol->dump() ).arg( mRender );
}
void QgsRendererCategory::toSld( QDomDocument &doc, QDomElement &element, QgsStringMap props ) const
{
if ( !mSymbol.get() || props.value( QStringLiteral( "attribute" ), QLatin1String( "" ) ).isEmpty() )
return;
QString attrName = props[ QStringLiteral( "attribute" )];
QDomElement ruleElem = doc.createElement( QStringLiteral( "se:Rule" ) );
element.appendChild( ruleElem );
QDomElement nameElem = doc.createElement( QStringLiteral( "se:Name" ) );
nameElem.appendChild( doc.createTextNode( mLabel ) );
ruleElem.appendChild( nameElem );
QDomElement descrElem = doc.createElement( QStringLiteral( "se:Description" ) );
QDomElement titleElem = doc.createElement( QStringLiteral( "se:Title" ) );
QString descrStr = QStringLiteral( "%1 is '%2'" ).arg( attrName, mValue.toString() );
titleElem.appendChild( doc.createTextNode( !mLabel.isEmpty() ? mLabel : descrStr ) );
descrElem.appendChild( titleElem );
ruleElem.appendChild( descrElem );
// create the ogc:Filter for the range
QString filterFunc = QStringLiteral( "%1 = '%2'" )
.arg( attrName.replace( '\"', QLatin1String( "\"\"" ) ),
mValue.toString().replace( '\'', QLatin1String( "''" ) ) );
QgsSymbolLayerUtils::createFunctionElement( doc, ruleElem, filterFunc );
// add the mix/max scale denoms if we got any from the callers
QgsSymbolLayerUtils::applyScaleDependency( doc, ruleElem, props );
mSymbol->toSld( doc, ruleElem, props );
}
///////////////////
QgsCategorizedSymbolRenderer::QgsCategorizedSymbolRenderer( const QString& attrName, const QgsCategoryList& categories )
: QgsFeatureRenderer( QStringLiteral( "categorizedSymbol" ) )
, mAttrName( attrName )
, mAttrNum( -1 )
, mCounting( false )
{
//important - we need a deep copy of the categories list, not a shared copy. This is required because
//QgsRendererCategory::symbol() is marked const, and so retrieving the symbol via this method does not
//trigger a detachment and copy of mCategories BUT that same method CAN be used to modify a symbol in place
Q_FOREACH ( const QgsRendererCategory& cat, categories )
{
if ( !cat.symbol() )
{
QgsDebugMsg( "invalid symbol in a category! ignoring..." );
}
mCategories << cat;
}
}
void QgsCategorizedSymbolRenderer::rebuildHash()
{
mSymbolHash.clear();
for ( int i = 0; i < mCategories.size(); ++i )
{
const QgsRendererCategory& cat = mCategories.at( i );
mSymbolHash.insert( cat.value().toString(), ( cat.renderState() || mCounting ) ? cat.symbol() : skipRender() );
}
}
QgsSymbol*QgsCategorizedSymbolRenderer::skipRender()
{
static QgsMarkerSymbol* skipRender = nullptr;
if ( !skipRender )
skipRender = new QgsMarkerSymbol();
return skipRender;
}
QgsSymbol* QgsCategorizedSymbolRenderer::symbolForValue( const QVariant& value )
{
// TODO: special case for int, double
QHash<QString, QgsSymbol*>::const_iterator it = mSymbolHash.constFind( value.isNull() ? QLatin1String( "" ) : value.toString() );
if ( it == mSymbolHash.constEnd() )
{
if ( mSymbolHash.isEmpty() )
{
QgsDebugMsg( "there are no hashed symbols!!!" );
}
else
{
QgsDebugMsgLevel( "attribute value not found: " + value.toString(), 3 );
}
return nullptr;
}
return *it;
}
QgsSymbol* QgsCategorizedSymbolRenderer::symbolForFeature( QgsFeature& feature, QgsRenderContext &context )
{
return originalSymbolForFeature( feature, context );
}
QVariant QgsCategorizedSymbolRenderer::valueForFeature( QgsFeature& feature, QgsRenderContext &context ) const
{
QgsAttributes attrs = feature.attributes();
QVariant value;
if ( mAttrNum == -1 )
{
Q_ASSERT( mExpression );
value = mExpression->evaluate( &context.expressionContext() );
}
else
{
value = attrs.value( mAttrNum );
}
return value;
}
QgsSymbol* QgsCategorizedSymbolRenderer::originalSymbolForFeature( QgsFeature& feature, QgsRenderContext &context )
{
QVariant value = valueForFeature( feature, context );
// find the right symbol for the category
QgsSymbol *symbol = symbolForValue( value );
if ( symbol == skipRender() )
return nullptr;
if ( !symbol )
{
// if no symbol found use default one
return symbolForValue( QVariant( "" ) );
}
return symbol;
}
int QgsCategorizedSymbolRenderer::categoryIndexForValue( const QVariant& val )
{
for ( int i = 0; i < mCategories.count(); i++ )
{
if ( mCategories[i].value() == val )
return i;
}
return -1;
}
int QgsCategorizedSymbolRenderer::categoryIndexForLabel( const QString& val )
{
int idx = -1;
for ( int i = 0; i < mCategories.count(); i++ )
{
if ( mCategories[i].label() == val )
{
if ( idx != -1 )
return -1;
else
idx = i;
}
}
return idx;
}
bool QgsCategorizedSymbolRenderer::updateCategoryValue( int catIndex, const QVariant &value )
{
if ( catIndex < 0 || catIndex >= mCategories.size() )
return false;
mCategories[catIndex].setValue( value );
return true;
}
bool QgsCategorizedSymbolRenderer::updateCategorySymbol( int catIndex, QgsSymbol* symbol )
{
if ( catIndex < 0 || catIndex >= mCategories.size() )
return false;
mCategories[catIndex].setSymbol( symbol );
return true;
}
bool QgsCategorizedSymbolRenderer::updateCategoryLabel( int catIndex, const QString& label )
{
if ( catIndex < 0 || catIndex >= mCategories.size() )
return false;
mCategories[catIndex].setLabel( label );
return true;
}
bool QgsCategorizedSymbolRenderer::updateCategoryRenderState( int catIndex, bool render )
{
if ( catIndex < 0 || catIndex >= mCategories.size() )
return false;
mCategories[catIndex].setRenderState( render );
return true;
}
void QgsCategorizedSymbolRenderer::addCategory( const QgsRendererCategory &cat )
{
if ( !cat.symbol() )
{
QgsDebugMsg( "invalid symbol in a category! ignoring..." );
return;
}
mCategories.append( cat );
}
bool QgsCategorizedSymbolRenderer::deleteCategory( int catIndex )
{
if ( catIndex < 0 || catIndex >= mCategories.size() )
return false;
mCategories.removeAt( catIndex );
return true;
}
void QgsCategorizedSymbolRenderer::deleteAllCategories()
{
mCategories.clear();
}
void QgsCategorizedSymbolRenderer::moveCategory( int from, int to )
{
if ( from < 0 || from >= mCategories.size() || to < 0 || to >= mCategories.size() ) return;
mCategories.move( from, to );
}
bool valueLessThan( const QgsRendererCategory &c1, const QgsRendererCategory &c2 )
{
return qgsVariantLessThan( c1.value(), c2.value() );
}
bool valueGreaterThan( const QgsRendererCategory &c1, const QgsRendererCategory &c2 )
{
return qgsVariantGreaterThan( c1.value(), c2.value() );
}
void QgsCategorizedSymbolRenderer::sortByValue( Qt::SortOrder order )
{
if ( order == Qt::AscendingOrder )
{
std::sort( mCategories.begin(), mCategories.end(), valueLessThan );
}
else
{
std::sort( mCategories.begin(), mCategories.end(), valueGreaterThan );
}
}
bool labelLessThan( const QgsRendererCategory &c1, const QgsRendererCategory &c2 )
{
return QString::localeAwareCompare( c1.label(), c2.label() ) < 0;
}
bool labelGreaterThan( const QgsRendererCategory &c1, const QgsRendererCategory &c2 )
{
return !labelLessThan( c1, c2 );
}
void QgsCategorizedSymbolRenderer::sortByLabel( Qt::SortOrder order )
{
if ( order == Qt::AscendingOrder )
{
std::sort( mCategories.begin(), mCategories.end(), labelLessThan );
}
else
{
std::sort( mCategories.begin(), mCategories.end(), labelGreaterThan );
}
}
void QgsCategorizedSymbolRenderer::startRender( QgsRenderContext& context, const QgsFields& fields )
{
mCounting = context.rendererScale() == 0.0;
// make sure that the hash table is up to date
rebuildHash();
// find out classification attribute index from name
mAttrNum = fields.lookupField( mAttrName );
if ( mAttrNum == -1 )
{
mExpression.reset( new QgsExpression( mAttrName ) );
mExpression->prepare( &context.expressionContext() );
}
Q_FOREACH ( const QgsRendererCategory& cat, mCategories )
{
cat.symbol()->startRender( context, fields );
}
Q_FOREACH ( QgsSymbol *sym, mSymbolHash.values() )
{
sym->startRender( context, fields );
}
return;
}
void QgsCategorizedSymbolRenderer::stopRender( QgsRenderContext& context )
{
Q_FOREACH ( const QgsRendererCategory& cat, mCategories )
{
cat.symbol()->stopRender( context );
}
Q_FOREACH ( QgsSymbol *sym, mSymbolHash.values() )
{
sym->stopRender( context );
}
mExpression.reset();
}
QSet<QString> QgsCategorizedSymbolRenderer::usedAttributes( const QgsRenderContext& context ) const
{
QSet<QString> attributes;
// mAttrName can contain either attribute name or an expression.
// Sometimes it is not possible to distinguish between those two,
// e.g. "a - b" can be both a valid attribute name or expression.
// Since we do not have access to fields here, try both options.
attributes << mAttrName;
QgsExpression testExpr( mAttrName );
if ( !testExpr.hasParserError() )
attributes.unite( testExpr.referencedColumns() );
QgsCategoryList::const_iterator catIt = mCategories.constBegin();
for ( ; catIt != mCategories.constEnd(); ++catIt )
{
QgsSymbol* catSymbol = catIt->symbol();
if ( catSymbol )
{
attributes.unite( catSymbol->usedAttributes( context ) );
}
}
return attributes;
}
QString QgsCategorizedSymbolRenderer::dump() const
{
QString s = QStringLiteral( "CATEGORIZED: idx %1\n" ).arg( mAttrName );
for ( int i = 0; i < mCategories.count(); i++ )
s += mCategories[i].dump();
return s;
}
QgsCategorizedSymbolRenderer* QgsCategorizedSymbolRenderer::clone() const
{
QgsCategorizedSymbolRenderer* r = new QgsCategorizedSymbolRenderer( mAttrName, mCategories );
if ( mSourceSymbol )
r->setSourceSymbol( mSourceSymbol->clone() );
if ( mSourceColorRamp )
{
r->setSourceColorRamp( mSourceColorRamp->clone() );
}
r->setUsingSymbolLevels( usingSymbolLevels() );
copyRendererData( r );
return r;
}
void QgsCategorizedSymbolRenderer::toSld( QDomDocument &doc, QDomElement &element, const QgsStringMap &props ) const
{
QgsStringMap newProps = props;
newProps[ QStringLiteral( "attribute" )] = mAttrName;
// create a Rule for each range
for ( QgsCategoryList::const_iterator it = mCategories.constBegin(); it != mCategories.constEnd(); ++it )
{
QgsStringMap catProps( newProps );
it->toSld( doc, element, catProps );
}
}
QString QgsCategorizedSymbolRenderer::filter( const QgsFields& fields )
{
int attrNum = fields.lookupField( mAttrName );
bool isExpression = ( attrNum == -1 );
bool hasDefault = false;
bool defaultActive = false;
bool allActive = true;
bool noneActive = true;
//we need to build lists of both inactive and active values, as either list may be required
//depending on whether the default category is active or not
QString activeValues;
QString inactiveValues;
Q_FOREACH ( const QgsRendererCategory& cat, mCategories )
{
if ( cat.value() == "" )
{
hasDefault = true;
defaultActive = cat.renderState();
}
noneActive = noneActive && !cat.renderState();
allActive = allActive && cat.renderState();
QVariant::Type valType = isExpression ? cat.value().type() : fields.at( attrNum ).type();
QString value = QgsExpression::quotedValue( cat.value(), valType );
if ( !cat.renderState() )
{
if ( cat.value() != "" )
{
if ( !inactiveValues.isEmpty() )
inactiveValues.append( ',' );
inactiveValues.append( value );
}
}
else
{
if ( cat.value() != "" )
{
if ( !activeValues.isEmpty() )
activeValues.append( ',' );
activeValues.append( value );
}
}
}
QString attr = isExpression ? mAttrName : QStringLiteral( "\"%1\"" ).arg( mAttrName );
if ( allActive && hasDefault )
{
return QString();
}
else if ( noneActive )
{
return QStringLiteral( "FALSE" );
}
else if ( defaultActive )
{
return QStringLiteral( "(%1) NOT IN (%2) OR (%1) IS NULL" ).arg( attr, inactiveValues );
}
else
{
return QStringLiteral( "(%1) IN (%2)" ).arg( attr, activeValues );
}
}
QgsSymbolList QgsCategorizedSymbolRenderer::symbols( QgsRenderContext &context )
{
Q_UNUSED( context );
QgsSymbolList lst;
lst.reserve( mCategories.count() );
Q_FOREACH ( const QgsRendererCategory& cat, mCategories )
{
lst.append( cat.symbol() );
}
return lst;
}
QgsFeatureRenderer* QgsCategorizedSymbolRenderer::create( QDomElement& element )
{
QDomElement symbolsElem = element.firstChildElement( QStringLiteral( "symbols" ) );
if ( symbolsElem.isNull() )
return nullptr;
QDomElement catsElem = element.firstChildElement( QStringLiteral( "categories" ) );
if ( catsElem.isNull() )
return nullptr;
QgsSymbolMap symbolMap = QgsSymbolLayerUtils::loadSymbols( symbolsElem );
QgsCategoryList cats;
QDomElement catElem = catsElem.firstChildElement();
while ( !catElem.isNull() )
{
if ( catElem.tagName() == QLatin1String( "category" ) )
{
QVariant value = QVariant( catElem.attribute( QStringLiteral( "value" ) ) );
QString symbolName = catElem.attribute( QStringLiteral( "symbol" ) );
QString label = catElem.attribute( QStringLiteral( "label" ) );
bool render = catElem.attribute( QStringLiteral( "render" ) ) != QLatin1String( "false" );
if ( symbolMap.contains( symbolName ) )
{
QgsSymbol* symbol = symbolMap.take( symbolName );
cats.append( QgsRendererCategory( value, symbol, label, render ) );
}
}
catElem = catElem.nextSiblingElement();
}
QString attrName = element.attribute( QStringLiteral( "attr" ) );
QgsCategorizedSymbolRenderer* r = new QgsCategorizedSymbolRenderer( attrName, cats );
// delete symbols if there are any more
QgsSymbolLayerUtils::clearSymbolMap( symbolMap );
// try to load source symbol (optional)
QDomElement sourceSymbolElem = element.firstChildElement( QStringLiteral( "source-symbol" ) );
if ( !sourceSymbolElem.isNull() )
{
QgsSymbolMap sourceSymbolMap = QgsSymbolLayerUtils::loadSymbols( sourceSymbolElem );
if ( sourceSymbolMap.contains( QStringLiteral( "0" ) ) )
{
r->setSourceSymbol( sourceSymbolMap.take( QStringLiteral( "0" ) ) );
}
QgsSymbolLayerUtils::clearSymbolMap( sourceSymbolMap );
}
// try to load color ramp (optional)
QDomElement sourceColorRampElem = element.firstChildElement( QStringLiteral( "colorramp" ) );
if ( !sourceColorRampElem.isNull() && sourceColorRampElem.attribute( QStringLiteral( "name" ) ) == QLatin1String( "[source]" ) )
{
r->setSourceColorRamp( QgsSymbolLayerUtils::loadColorRamp( sourceColorRampElem ) );
}
QDomElement rotationElem = element.firstChildElement( QStringLiteral( "rotation" ) );
if ( !rotationElem.isNull() && !rotationElem.attribute( QStringLiteral( "field" ) ).isEmpty() )
{
Q_FOREACH ( const QgsRendererCategory& cat, r->mCategories )
{
convertSymbolRotation( cat.symbol(), rotationElem.attribute( QStringLiteral( "field" ) ) );
}
if ( r->mSourceSymbol )
{
convertSymbolRotation( r->mSourceSymbol.get(), rotationElem.attribute( QStringLiteral( "field" ) ) );
}
}
QDomElement sizeScaleElem = element.firstChildElement( QStringLiteral( "sizescale" ) );
if ( !sizeScaleElem.isNull() && !sizeScaleElem.attribute( QStringLiteral( "field" ) ).isEmpty() )
{
Q_FOREACH ( const QgsRendererCategory& cat, r->mCategories )
{
convertSymbolSizeScale( cat.symbol(),
QgsSymbolLayerUtils::decodeScaleMethod( sizeScaleElem.attribute( QStringLiteral( "scalemethod" ) ) ),
sizeScaleElem.attribute( QStringLiteral( "field" ) ) );
}
if ( r->mSourceSymbol && r->mSourceSymbol->type() == QgsSymbol::Marker )
{
convertSymbolSizeScale( r->mSourceSymbol.get(),
QgsSymbolLayerUtils::decodeScaleMethod( sizeScaleElem.attribute( QStringLiteral( "scalemethod" ) ) ),
sizeScaleElem.attribute( QStringLiteral( "field" ) ) );
}
}
// TODO: symbol levels
return r;
}
QDomElement QgsCategorizedSymbolRenderer::save( QDomDocument& doc )
{
QDomElement rendererElem = doc.createElement( RENDERER_TAG_NAME );
rendererElem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "categorizedSymbol" ) );
rendererElem.setAttribute( QStringLiteral( "symbollevels" ), ( mUsingSymbolLevels ? "1" : "0" ) );
rendererElem.setAttribute( QStringLiteral( "forceraster" ), ( mForceRaster ? "1" : "0" ) );
rendererElem.setAttribute( QStringLiteral( "attr" ), mAttrName );
// categories
if ( !mCategories.isEmpty() )
{
int i = 0;
QgsSymbolMap symbols;
QDomElement catsElem = doc.createElement( QStringLiteral( "categories" ) );
QgsCategoryList::const_iterator it = mCategories.constBegin();
for ( ; it != mCategories.constEnd(); ++it )
{
const QgsRendererCategory& cat = *it;
QString symbolName = QString::number( i );
symbols.insert( symbolName, cat.symbol() );
QDomElement catElem = doc.createElement( QStringLiteral( "category" ) );
catElem.setAttribute( QStringLiteral( "value" ), cat.value().toString() );
catElem.setAttribute( QStringLiteral( "symbol" ), symbolName );
catElem.setAttribute( QStringLiteral( "label" ), cat.label() );
catElem.setAttribute( QStringLiteral( "render" ), cat.renderState() ? "true" : "false" );
catsElem.appendChild( catElem );
i++;
}
rendererElem.appendChild( catsElem );
// save symbols
QDomElement symbolsElem = QgsSymbolLayerUtils::saveSymbols( symbols, QStringLiteral( "symbols" ), doc );
rendererElem.appendChild( symbolsElem );
}
// save source symbol
if ( mSourceSymbol )
{
QgsSymbolMap sourceSymbols;
sourceSymbols.insert( QStringLiteral( "0" ), mSourceSymbol.get() );
QDomElement sourceSymbolElem = QgsSymbolLayerUtils::saveSymbols( sourceSymbols, QStringLiteral( "source-symbol" ), doc );
rendererElem.appendChild( sourceSymbolElem );
}
// save source color ramp
if ( mSourceColorRamp )
{
QDomElement colorRampElem = QgsSymbolLayerUtils::saveColorRamp( QStringLiteral( "[source]" ), mSourceColorRamp.get(), doc );
rendererElem.appendChild( colorRampElem );
}
QDomElement rotationElem = doc.createElement( QStringLiteral( "rotation" ) );
rendererElem.appendChild( rotationElem );
QDomElement sizeScaleElem = doc.createElement( QStringLiteral( "sizescale" ) );
rendererElem.appendChild( sizeScaleElem );
if ( mPaintEffect && !QgsPaintEffectRegistry::isDefaultStack( mPaintEffect ) )
mPaintEffect->saveProperties( doc, rendererElem );
if ( !mOrderBy.isEmpty() )
{
QDomElement orderBy = doc.createElement( QStringLiteral( "orderby" ) );
mOrderBy.save( orderBy );
rendererElem.appendChild( orderBy );
}
rendererElem.setAttribute( QStringLiteral( "enableorderby" ), ( mOrderByEnabled ? "1" : "0" ) );
return rendererElem;
}
QgsLegendSymbologyList QgsCategorizedSymbolRenderer::legendSymbologyItems( QSize iconSize )
{
QgsLegendSymbologyList lst;
int count = categories().count();
lst.reserve( count );
for ( int i = 0; i < count; i++ )
{
const QgsRendererCategory& cat = categories()[i];
QPixmap pix = QgsSymbolLayerUtils::symbolPreviewPixmap( cat.symbol(), iconSize );
lst << qMakePair( cat.label(), pix );
}
return lst;
}
QgsLegendSymbolList QgsCategorizedSymbolRenderer::legendSymbolItems( double scaleDenominator, const QString& rule )
{
Q_UNUSED( scaleDenominator );
QgsLegendSymbolList lst;
Q_FOREACH ( const QgsRendererCategory& cat, mCategories )
{
if ( rule.isEmpty() || cat.label() == rule )
{
lst << qMakePair( cat.label(), cat.symbol() );
}
}
return lst;
}
QgsLegendSymbolListV2 QgsCategorizedSymbolRenderer::legendSymbolItemsV2() const
{
QgsLegendSymbolListV2 lst;
if ( mSourceSymbol && mSourceSymbol->type() == QgsSymbol::Marker )
{
// check that all symbols that have the same size expression
QgsProperty ddSize;
Q_FOREACH ( const QgsRendererCategory& category, mCategories )
{
const QgsMarkerSymbol * symbol = static_cast<const QgsMarkerSymbol *>( category.symbol() );
if ( ddSize )
{
QgsProperty sSize( symbol->dataDefinedSize() );
if ( sSize != ddSize )
{
// no common size expression
return QgsFeatureRenderer::legendSymbolItemsV2();
}
}
else
{
ddSize = symbol->dataDefinedSize();
}
}
if ( !ddSize || !ddSize.isActive() )
{
return QgsFeatureRenderer::legendSymbolItemsV2();
}
if ( const QgsSizeScaleTransformer* sizeTransformer = dynamic_cast< const QgsSizeScaleTransformer* >( ddSize.transformer() ) )
{
QgsLegendSymbolItem title( nullptr, ddSize.propertyType() == QgsProperty::ExpressionBasedProperty ? ddSize.expressionString()
: ddSize.field(), QString() );
lst << title;
Q_FOREACH ( double v, QgsSymbolLayerUtils::prettyBreaks( sizeTransformer->minValue(), sizeTransformer->maxValue(), 4 ) )
{
QgsLegendSymbolItem si( mSourceSymbol.get(), QString::number( v ), QString() );
QgsMarkerSymbol * s = static_cast<QgsMarkerSymbol *>( si.symbol() );
s->setDataDefinedSize( QgsProperty() );
s->setSize( sizeTransformer->size( v ) );
lst << si;
}
// now list the categorized symbols
const QgsLegendSymbolListV2 list2 = QgsFeatureRenderer::legendSymbolItemsV2() ;
Q_FOREACH ( const QgsLegendSymbolItem& item, list2 )
lst << item;
return lst;
}
}
return QgsFeatureRenderer::legendSymbolItemsV2();
}
QSet<QString> QgsCategorizedSymbolRenderer::legendKeysForFeature( QgsFeature& feature, QgsRenderContext& context )
{
QString value = valueForFeature( feature, context ).toString();
int i = 0;
Q_FOREACH ( const QgsRendererCategory& cat, mCategories )
{
if ( value == cat.value() )
{
if ( cat.renderState() )
return QSet< QString >() << QString::number( i );
else
return QSet< QString >();
}
i++;
}
return QSet< QString >();
}
QgsSymbol* QgsCategorizedSymbolRenderer::sourceSymbol()
{
return mSourceSymbol.get();
}
void QgsCategorizedSymbolRenderer::setSourceSymbol( QgsSymbol* sym )
{
mSourceSymbol.reset( sym );
}
QgsColorRamp* QgsCategorizedSymbolRenderer::sourceColorRamp()
{
return mSourceColorRamp.get();
}
void QgsCategorizedSymbolRenderer::setSourceColorRamp( QgsColorRamp* ramp )
{
mSourceColorRamp.reset( ramp );
}
void QgsCategorizedSymbolRenderer::updateColorRamp( QgsColorRamp* ramp )
{
setSourceColorRamp( ramp );
double num = mCategories.count() - 1;
double count = 0;
QgsRandomColorRamp* randomRamp = dynamic_cast<QgsRandomColorRamp*>( ramp );
if ( randomRamp )
{
//ramp is a random colors ramp, so inform it of the total number of required colors
//this allows the ramp to pregenerate a set of visually distinctive colors
randomRamp->setTotalColorCount( mCategories.count() );
}
Q_FOREACH ( const QgsRendererCategory &cat, mCategories )
{
double value = count / num;
cat.symbol()->setColor( mSourceColorRamp->color( value ) );
count += 1;
}
}
void QgsCategorizedSymbolRenderer::updateSymbols( QgsSymbol * sym )
{
int i = 0;
Q_FOREACH ( const QgsRendererCategory& cat, mCategories )
{
QgsSymbol* symbol = sym->clone();
symbol->setColor( cat.symbol()->color() );
updateCategorySymbol( i, symbol );
++i;
}
setSourceSymbol( sym->clone() );
}
bool QgsCategorizedSymbolRenderer::legendSymbolItemsCheckable() const
{
return true;
}
bool QgsCategorizedSymbolRenderer::legendSymbolItemChecked( const QString& key )
{
bool ok;
int index = key.toInt( &ok );
if ( ok && index >= 0 && index < mCategories.size() )
return mCategories.at( index ).renderState();
else
return true;
}
void QgsCategorizedSymbolRenderer::setLegendSymbolItem( const QString& key, QgsSymbol* symbol )
{
bool ok;
int index = key.toInt( &ok );
if ( ok )
updateCategorySymbol( index, symbol );
else
delete symbol;
}
void QgsCategorizedSymbolRenderer::checkLegendSymbolItem( const QString& key, bool state )
{
bool ok;
int index = key.toInt( &ok );
if ( ok )
updateCategoryRenderState( index, state );
}
QgsCategorizedSymbolRenderer* QgsCategorizedSymbolRenderer::convertFromRenderer( const QgsFeatureRenderer *renderer )
{
QgsCategorizedSymbolRenderer* r = nullptr;
if ( renderer->type() == QLatin1String( "categorizedSymbol" ) )
{
r = dynamic_cast<QgsCategorizedSymbolRenderer*>( renderer->clone() );
}
else if ( renderer->type() == QLatin1String( "pointDisplacement" ) || renderer->type() == QLatin1String( "pointCluster" ) )
{
const QgsPointDistanceRenderer* pointDistanceRenderer = dynamic_cast<const QgsPointDistanceRenderer*>( renderer );
if ( pointDistanceRenderer )
r = convertFromRenderer( pointDistanceRenderer->embeddedRenderer() );
}
else if ( renderer->type() == QLatin1String( "invertedPolygonRenderer" ) )
{
const QgsInvertedPolygonRenderer* invertedPolygonRenderer = dynamic_cast<const QgsInvertedPolygonRenderer*>( renderer );
if ( invertedPolygonRenderer )
r = convertFromRenderer( invertedPolygonRenderer->embeddedRenderer() );
}
// If not one of the specifically handled renderers, then just grab the symbol from the renderer
// Could have applied this to specific renderer types (singleSymbol, graduatedSymbol)
if ( !r )
{
r = new QgsCategorizedSymbolRenderer( QLatin1String( "" ), QgsCategoryList() );
QgsRenderContext context;
QgsSymbolList symbols = const_cast<QgsFeatureRenderer *>( renderer )->symbols( context );
if ( !symbols.isEmpty() )
{
r->setSourceSymbol( symbols.at( 0 )->clone() );
}
}
r->setOrderBy( renderer->orderBy() );
r->setOrderByEnabled( renderer->orderByEnabled() );
return r;
}