Skip to content
Permalink
Browse files

[FEATURE] Add a generic numeric transform assistant for easy scaling …

…of values
  • Loading branch information
nyalldawson committed Feb 14, 2017
1 parent 2df37a2 commit a30fe357f160972712733a952bc2b2c5a8175dfc
@@ -32,6 +32,9 @@ QgsPropertyTransformer* QgsPropertyTransformer::create( QgsPropertyTransformer::
QgsPropertyTransformer* transformer = nullptr;
switch ( type )
{
case GenericNumericTransformer:
transformer = new QgsGenericNumericTransformer();
break;
case SizeScaleTransformer:
transformer = new QgsSizeScaleTransformer();
break;
@@ -74,6 +77,172 @@ bool QgsPropertyTransformer::readXml( const QDomElement &transformerElem, const
return true;
}

//
// QgsGenericNumericTransformer
//

QgsGenericNumericTransformer::QgsGenericNumericTransformer( double minValue, double maxValue, double minOutput, double maxOutput, double nullOutput, double exponent )
: QgsPropertyTransformer( minValue, maxValue )
, mMinOutput( minOutput )
, mMaxOutput( maxOutput )
, mNullOutput( nullOutput )
, mExponent( exponent )
{}

QgsGenericNumericTransformer *QgsGenericNumericTransformer::clone()
{
return new QgsGenericNumericTransformer( mMinValue,
mMaxValue,
mMinOutput,
mMaxOutput,
mNullOutput,
mExponent );
}

bool QgsGenericNumericTransformer::writeXml( QDomElement &transformerElem, QDomDocument &doc ) const
{
if ( !QgsPropertyTransformer::writeXml( transformerElem, doc ) )
return false;

transformerElem.setAttribute( "minOutput", QString::number( mMinOutput ) );
transformerElem.setAttribute( "maxOutput", QString::number( mMaxOutput ) );
transformerElem.setAttribute( "nullOutput", QString::number( mNullOutput ) );
transformerElem.setAttribute( "exponent", QString::number( mExponent ) );

return true;
}

bool QgsGenericNumericTransformer::readXml( const QDomElement &transformerElem, const QDomDocument &doc )
{
if ( !QgsPropertyTransformer::readXml( transformerElem, doc ) )
return false;

mMinOutput = transformerElem.attribute( "minOutput", "0.0" ).toDouble();
mMaxOutput = transformerElem.attribute( "maxOutput", "1.0" ).toDouble();
mNullOutput = transformerElem.attribute( "nullOutput", "0.0" ).toDouble();
mExponent = transformerElem.attribute( "exponent", "1.0" ).toDouble();
return true;
}

double QgsGenericNumericTransformer::value( double input ) const
{
if ( qgsDoubleNear( mExponent, 1.0 ) )
return mMinOutput + ( qBound( mMinValue, input, mMaxValue ) - mMinValue ) * ( mMaxOutput - mMinOutput ) / ( mMaxValue - mMinValue );
else
return mMinOutput + qPow( qBound( mMinValue, input, mMaxValue ) - mMinValue, mExponent ) * ( mMaxOutput - mMinOutput ) / qPow( mMaxValue - mMinValue, mExponent );
}

QVariant QgsGenericNumericTransformer::transform( const QgsExpressionContext& context, const QVariant& v ) const
{
Q_UNUSED( context );

if ( v.isNull() )
return mNullOutput;

bool ok;
double dblValue = v.toDouble( &ok );

if ( ok )
{
//apply scaling to value
return value( dblValue );
}
else
{
return v;
}
}

QString QgsGenericNumericTransformer::toExpression( const QString& baseExpression ) const
{
QString minValueString = QString::number( mMinValue );
QString maxValueString = QString::number( mMaxValue );
QString minOutputString = QString::number( mMinOutput );
QString maxOutputString = QString::number( mMaxOutput );
QString nullOutputString = QString::number( mNullOutput );
QString exponentString = QString::number( mExponent );

if ( qgsDoubleNear( mExponent, 1.0 ) )
return QStringLiteral( "coalesce(scale_linear(%1, %2, %3, %4, %5), %6)" ).arg( baseExpression, minValueString, maxValueString, minOutputString, maxOutputString, nullOutputString );
else
return QStringLiteral( "coalesce(scale_exp(%1, %2, %3, %4, %5, %6), %7)" ).arg( baseExpression, minValueString, maxValueString, minOutputString, maxOutputString, exponentString, nullOutputString );
}

QgsGenericNumericTransformer* QgsGenericNumericTransformer::fromExpression( const QString& expression, QString& baseExpression, QString& fieldName )
{
bool ok = false;

double nullValue = 0.0;
double exponent = 1.0;

baseExpression.clear();
fieldName.clear();

QgsExpression e( expression );

if ( !e.rootNode() )
return nullptr;

const QgsExpression::NodeFunction * f = dynamic_cast<const QgsExpression::NodeFunction*>( e.rootNode() );
if ( !f )
return nullptr;

QList<QgsExpression::Node*> args = f->args()->list();

// the scale function may be enclosed in a coalesce(expr, 0) to avoid NULL value
// to be drawn with the default size
if ( "coalesce" == QgsExpression::Functions()[f->fnIndex()]->name() )
{
f = dynamic_cast<const QgsExpression::NodeFunction*>( args[0] );
if ( !f )
return nullptr;
nullValue = QgsExpression( args[1]->dump() ).evaluate().toDouble( &ok );
if ( ! ok )
return nullptr;
args = f->args()->list();
}

if ( "scale_linear" == QgsExpression::Functions()[f->fnIndex()]->name() )
{
exponent = 1.0;
}
else if ( "scale_exp" == QgsExpression::Functions()[f->fnIndex()]->name() )
{
exponent = QgsExpression( args[5]->dump() ).evaluate().toDouble( &ok );
}
else
{
return nullptr;
}

bool expOk = true;
double minValue = QgsExpression( args[1]->dump() ).evaluate().toDouble( &ok );
expOk &= ok;
double maxValue = QgsExpression( args[2]->dump() ).evaluate().toDouble( &ok );
expOk &= ok;
double minOutput = QgsExpression( args[3]->dump() ).evaluate().toDouble( &ok );
expOk &= ok;
double maxOutput = QgsExpression( args[4]->dump() ).evaluate().toDouble( &ok );
expOk &= ok;

if ( !expOk )
{
return nullptr;
}

if ( args[0]->nodeType() == QgsExpression::ntColumnRef )
{
fieldName = static_cast< QgsExpression::NodeColumnRef* >( args[0] )->name();
}
else
{
baseExpression = args[0]->dump();
}
return new QgsGenericNumericTransformer( minValue, maxValue, minOutput, maxOutput, nullValue, exponent );
}



//
// QgsSizeScaleProperty
//
@@ -417,4 +586,3 @@ void QgsColorRampTransformer::setColorRamp( QgsColorRamp* ramp )
{
mGradientRamp.reset( ramp );
}

@@ -44,6 +44,7 @@ class CORE_EXPORT QgsPropertyTransformer
//! Transformer types
enum Type
{
GenericNumericTransformer, //!< Generic transformer for numeric values (QgsGenericNumericTransformer)
SizeScaleTransformer, //!< Size scaling transformer (QgsSizeScaleTransformer)
ColorRampTransformer, //!< Color ramp transformer (QgsColorRampTransformer)
};
@@ -157,6 +158,124 @@ class CORE_EXPORT QgsPropertyTransformer

};

/**
* \ingroup core
* \class QgsGenericNumericTransformer
* \brief QgsPropertyTransformer subclass for scaling an input numeric value into an output numeric value.
* \note Added in version 3.0
*/

class CORE_EXPORT QgsGenericNumericTransformer : public QgsPropertyTransformer
{
public:

/**
* Constructor for QgsGenericNumericTransformer.
* @param minValue minimum expected input value
* @param maxValue maximum expected input value
* @param minOutput minimum value to return
* @param maxOutput maximum value to return
* @param nullOutput value to return for null inputs
* @param exponent optional exponential for non-linear scaling
*/
QgsGenericNumericTransformer( double minValue = 0.0,
double maxValue = 1.0,
double minOutput = 0.0,
double maxOutput = 1.0,
double nullOutput = 0.0,
double exponent = 1.0 );

virtual Type transformerType() const override { return GenericNumericTransformer; }
virtual QgsGenericNumericTransformer* clone() override;
virtual bool writeXml( QDomElement& transformerElem, QDomDocument& doc ) const override;
virtual bool readXml( const QDomElement& transformerElem, const QDomDocument& doc ) override;
virtual QVariant transform( const QgsExpressionContext& context, const QVariant& value ) const override;
virtual QString toExpression( const QString& baseExpression ) const override;

/**
* Attempts to parse an expression into a corresponding QgsSizeScaleTransformer.
* @param expression expression to parse
* @param baseExpression will be set to the component of the source expression which
* is used to calculate the input to the property transformer. This will be set to an
* empty string if a field reference is the transformer input.
* @param fieldName will be set to a field name which is used to calculate the input
* to the property transformer. This will be set to an
* empty string if an expression is the transformer input.
* @returns corresponding QgsSizeScaleTransformer, or nullptr if expression could not
* be parsed to a size scale transformer.
*/
static QgsGenericNumericTransformer* fromExpression( const QString& expression, QString& baseExpression, QString& fieldName );

/**
* Calculates the size corresponding to a specific value.
* @param value value to calculate size for
* @returns calculated size using size scale transformer's parameters and type
*/
double value( double input ) const;

/**
* Returns the minimum calculated size.
* @see setMinSize()
* @see maxSize()
*/
double minOutputValue() const { return mMinOutput; }

/**
* Sets the minimum calculated size.
* @param size minimum size
* @see minSize()
* @see setMaxSize()
*/
void setMinOutputValue( double size ) { mMinOutput = size; }

/**
* Returns the maximum calculated size.
* @see minSize()
*/
double maxOutputValue() const { return mMaxOutput; }

/**
* Sets the maximum calculated size.
* @param size maximum size
* @see maxSize()
* @see setMinSize()
*/
void setMaxOutputValue( double size ) { mMaxOutput = size; }

/**
* Returns the size value when an expression evaluates to NULL.
* @see setNullSize()
*/
double nullOutputValue() const { return mNullOutput; }

/**
* Sets the size value for when an expression evaluates to NULL.
* @param size null size
* @see nullSize()
*/
void setNullOutputValue( double size ) { mNullOutput = size; }

/**
* Returns the exponent for an exponential expression.
* @see setExponent()
* @see type()
*/
double exponent() const { return mExponent; }

/**
* Sets the exponent for an exponential expression.
* @param exponent exponent
* @see exponent()
*/
void setExponent( double exponent ) { mExponent = exponent; }

private:
double mMinOutput;
double mMaxOutput;
double mNullOutput;
double mExponent;

};

/**
* \ingroup core

0 comments on commit a30fe35

Please sign in to comment.
You can’t perform that action at this time.