Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Merge pull request #33262 from 3nids/log_impr
Classification methods: save/read parameters, improve negative value handling in log method
  • Loading branch information
3nids committed Dec 10, 2019
2 parents 65eed59 + ba3c443 commit c5766dc
Show file tree
Hide file tree
Showing 8 changed files with 109 additions and 38 deletions.
Expand Up @@ -21,6 +21,14 @@ Implementation of a logarithmic scale method
#include "qgsclassificationlogarithmic.h"
%End
public:

enum NegativeValueHandling
{
NoHandling,
Discard,
PrependBreak
};

QgsClassificationLogarithmic();
virtual QgsClassificationMethod *clone() const;

Expand Down
Expand Up @@ -311,7 +311,13 @@ Defines the values of the additional parameters
.. versionadded:: 3.12
%End

QVariantMap parameterValues() const;
%Docstring
Returns the values of the processing parameters.
One could use QgsProcessingParameters.parameterAsXxxx to retrieve the actual value of a parameter.

.. versionadded:: 3.12
%End

static const int MAX_PRECISION;
static const int MIN_PRECISION;
Expand Down Expand Up @@ -339,12 +345,6 @@ The paramaeter is a processing parameter which will allow its configuration in t
Python parameters are not supported.

.. versionadded:: 3.12
%End

QVariantMap parameterValues() const;
%Docstring
Returns the values of the processing parameters.
One could use QgsProcessingParameters.parameterAsXxxx to retrieve the actual value of a parameter.
%End

};
Expand Down
63 changes: 44 additions & 19 deletions src/core/classification/qgsclassificationlogarithmic.cpp
Expand Up @@ -24,7 +24,7 @@
QgsClassificationLogarithmic::QgsClassificationLogarithmic()
: QgsClassificationMethod( NoFlag, 0 )
{
QgsProcessingParameterBoolean *param = new QgsProcessingParameterBoolean( QStringLiteral( "FILTER_ZERO_NEG_VALUES" ), QObject::tr( "Filter values ≤ 0" ), false );
QgsProcessingParameterEnum *param = new QgsProcessingParameterEnum( QStringLiteral( "ZERO_NEG_VALUES_HANDLE" ), QObject::tr( "Handling of 0 or negative values" ), QStringList() << QObject::tr( "no handling (faster)" ) << QObject::tr( "discard (slower)" ) << QObject::tr( "prepend range (slower)" ), false, 0 );
addParameter( param );
}

Expand Down Expand Up @@ -53,32 +53,52 @@ QIcon QgsClassificationLogarithmic::icon() const

QList<double> QgsClassificationLogarithmic::calculateBreaks( double &minimum, double &maximum, const QList<double> &values, int nclasses )
{
// not possible if only negative values
if ( maximum <= 0 )
return QList<double>();

QgsProcessingContext context;
bool filterZeroNeg = QgsProcessingParameters::parameterAsBool( parameterDefinition( QStringLiteral( "FILTER_ZERO_NEG_VALUES" ) ), parameterValues(), context );
const QgsProcessingParameterDefinition *def = parameterDefinition( QStringLiteral( "ZERO_NEG_VALUES_HANDLE" ) );
NegativeValueHandling nvh = static_cast< NegativeValueHandling >( QgsProcessingParameters::parameterAsEnum( def, parameterValues(), context ) );

if ( filterZeroNeg && minimum <= 0 )
double positiveMinimum = std::numeric_limits<double>::max();
if ( nvh != NegativeValueHandling::NoHandling && minimum <= 0 )
{
Q_ASSERT( values.count() );
minimum = std::numeric_limits<double>::max();
for ( int i = 0; i < values.count(); i++ )
if ( maximum > 0 )
{
for ( int i = 0; i < values.count(); i++ )
{
if ( values.at( i ) > 0 )
positiveMinimum = std::min( positiveMinimum, values.at( i ) );
}
}
if ( positiveMinimum == std::numeric_limits<double>::max() )
{
if ( values.at( i ) > 0 )
minimum = std::min( minimum, values.at( i ) );
// there is no usable values
if ( nvh == NegativeValueHandling::PrependBreak )
return QList<double>( {0} );
else
return QList<double>();
}
if ( minimum == std::numeric_limits<double>::max() )
return QList<double>();
}

QList<double> breaks;

if ( positiveMinimum != std::numeric_limits<double>::max() )
{
if ( nvh == NegativeValueHandling::PrependBreak )
breaks << std::floor( std::log10( positiveMinimum ) );
else if ( nvh == NegativeValueHandling::Discard )
minimum = positiveMinimum; // the minimum gets updated
}
else
{
positiveMinimum = minimum;
}

// get the min/max in log10 scale
double log_min = std::floor( std::log10( minimum ) );
double log_max = std::ceil( std::log10( maximum ) );
double logMin = std::floor( std::log10( positiveMinimum ) );
double logMax = std::ceil( std::log10( maximum ) );

// calculate pretty breaks
QList<double> breaks = QgsSymbolLayerUtils::prettyBreaks( log_min, log_max, nclasses );
breaks.append( QgsSymbolLayerUtils::prettyBreaks( logMin, logMax, nclasses ) );

// create the value
for ( int i = 0; i < breaks.count(); i++ )
Expand All @@ -89,8 +109,10 @@ QList<double> QgsClassificationLogarithmic::calculateBreaks( double &minimum, do

QString QgsClassificationLogarithmic::valueToLabel( double value ) const
{
QString label = QString( QStringLiteral( "10^%1" ) ).arg( std::log10( value ) );
return label;
if ( value <= 0 )
return QString( QStringLiteral( "%1" ) ).arg( value );
else
return QString( QStringLiteral( "10^%1" ) ).arg( std::log10( value ) );
}

QString QgsClassificationLogarithmic::labelForRange( double lowerValue, double upperValue, QgsClassificationMethod::ClassPosition position ) const
Expand All @@ -115,5 +137,8 @@ QString QgsClassificationLogarithmic::labelForRange( double lowerValue, double u
bool QgsClassificationLogarithmic::valuesRequired() const
{
QgsProcessingContext context;
return QgsProcessingParameters::parameterAsBool( parameterDefinition( QStringLiteral( "FILTER_ZERO_NEG_VALUES" ) ), parameterValues(), context );
const QgsProcessingParameterDefinition *def = parameterDefinition( QStringLiteral( "ZERO_NEG_VALUES_HANDLE" ) );
NegativeValueHandling nvh = static_cast< NegativeValueHandling >( QgsProcessingParameters::parameterAsEnum( def, parameterValues(), context ) );

return nvh != NegativeValueHandling::NoHandling;
}
12 changes: 12 additions & 0 deletions src/core/classification/qgsclassificationlogarithmic.h
Expand Up @@ -28,6 +28,18 @@
class CORE_EXPORT QgsClassificationLogarithmic : public QgsClassificationMethod
{
public:

/**
* Handling of negative and 0 values in the method
* \since QGIS 3.12
*/
enum NegativeValueHandling
{
NoHandling = 0, //!< No handling
Discard, //!< Negative values are discarded - this will require all values
PrependBreak //!< Prepend an extra break to include negative values - this will require all values
};

QgsClassificationLogarithmic();
QgsClassificationMethod *clone() const override;
QString name() const override;
Expand Down
15 changes: 14 additions & 1 deletion src/core/classification/qgsclassificationmethod.cpp
Expand Up @@ -22,6 +22,7 @@
#include "qgsgraduatedsymbolrenderer.h"
#include "qgsapplication.h"
#include "qgsclassificationmethodregistry.h"
#include "qgsxmlutils.h"

const int QgsClassificationMethod::MAX_PRECISION = 15;
const int QgsClassificationMethod::MIN_PRECISION = -6;
Expand Down Expand Up @@ -54,6 +55,7 @@ void QgsClassificationMethod::copyBase( QgsClassificationMethod *c ) const
c->setLabelFormat( mLabelFormat );
c->setLabelPrecision( mLabelPrecision );
c->setLabelTrimTrailingZeroes( mLabelTrimTrailingZeroes );
c->setParameterValues( mParameterValues );
}

QgsClassificationMethod *QgsClassificationMethod::create( const QDomElement &element, const QgsReadWriteContext &context )
Expand Down Expand Up @@ -83,6 +85,11 @@ QgsClassificationMethod *QgsClassificationMethod::create( const QDomElement &ele
method->setLabelTrimTrailingZeroes( trimTrailingZeroes );
}

// parameters (processing parameters)
QDomElement parametersElem = element.firstChildElement( QStringLiteral( "parameters" ) );
const QVariantMap parameterValues = QgsXmlUtils::readVariant( parametersElem.firstChildElement() ).toMap();
method->setParameterValues( parameterValues );

// Read specific properties from the implementation
QDomElement extraElem = element.firstChildElement( QStringLiteral( "extraInformation" ) );
if ( !extraElem.isNull() )
Expand Down Expand Up @@ -111,6 +118,11 @@ QDomElement QgsClassificationMethod::save( QDomDocument &doc, const QgsReadWrite
labelFormatElem.setAttribute( QStringLiteral( "trimtrailingzeroes" ), labelTrimTrailingZeroes() ? 1 : 0 );
methodElem.appendChild( labelFormatElem );

// parameters (processing parameters)
QDomElement parametersElem = doc.createElement( QStringLiteral( "parameters" ) );
parametersElem.appendChild( QgsXmlUtils::writeVariant( mParameterValues, doc ) );
methodElem.appendChild( parametersElem );

// extra information
QDomElement extraElem = doc.createElement( QStringLiteral( "extraInformation" ) );
writeXml( extraElem, context );
Expand Down Expand Up @@ -178,13 +190,14 @@ const QgsProcessingParameterDefinition *QgsClassificationMethod::parameterDefini
if ( def->name() == parameterName )
return def;
}
QgsMessageLog::logMessage( QStringLiteral( "No parameter definition found for %1 in %2 method." ).arg( parameterName ).arg( name() ) );
return nullptr;
}

void QgsClassificationMethod::setParameterValues( const QVariantMap &values )
{
mParameterValues = values;
for ( auto it = mParameterValues.begin(); it != mParameterValues.end(); ++it )
for ( auto it = mParameterValues.constBegin(); it != mParameterValues.constEnd(); ++it )
{
if ( !parameterDefinition( it.key() ) )
{
Expand Down
13 changes: 6 additions & 7 deletions src/core/classification/qgsclassificationmethod.h
Expand Up @@ -289,7 +289,12 @@ class CORE_EXPORT QgsClassificationMethod SIP_ABSTRACT
*/
void setParameterValues( const QVariantMap &values );


/**
* Returns the values of the processing parameters.
* One could use QgsProcessingParameters::parameterAsXxxx to retrieve the actual value of a parameter.
* \since QGIS 3.12
*/
QVariantMap parameterValues() const {return mParameterValues;}

static const int MAX_PRECISION;
static const int MIN_PRECISION;
Expand All @@ -311,12 +316,6 @@ class CORE_EXPORT QgsClassificationMethod SIP_ABSTRACT
*/
void addParameter( QgsProcessingParameterDefinition *definition SIP_TRANSFER );

/**
* Returns the values of the processing parameters.
* One could use QgsProcessingParameters::parameterAsXxxx to retrieve the actual value of a parameter.
*/
QVariantMap parameterValues() const {return mParameterValues;}

private:

/**
Expand Down
13 changes: 12 additions & 1 deletion src/gui/symbology/qgsgraduatedsymbolrendererwidget.cpp
Expand Up @@ -705,6 +705,14 @@ void QgsGraduatedSymbolRendererWidget::updateUiFromRenderer( bool updateCount )
txtLegendFormat->setText( method->labelFormat() );
spinPrecision->setValue( method->labelPrecision() );
cbxTrimTrailingZeroes->setChecked( method->labelTrimTrailingZeroes() );

QgsProcessingContext context;
for ( const auto &ppww : qgis::as_const( mParameterWidgetWrappers ) )
{
const QgsProcessingParameterDefinition *def = ppww->parameterDefinition();
QVariant value = method->parameterValues().value( def->name(), def->defaultValue() );
ppww->setParameterValue( value, context );
}
}

// Only update class count if different - otherwise typing value gets very messy
Expand Down Expand Up @@ -827,14 +835,17 @@ void QgsGraduatedSymbolRendererWidget::updateMethodParameters()
QgsClassificationMethod *method = QgsApplication::classificationMethodRegistry()->method( methodId );
Q_ASSERT( method );

// todo need more?
// need more context?
QgsProcessingContext context;

for ( const QgsProcessingParameterDefinition *def : method->parameterDefinitions() )
{
QgsAbstractProcessingParameterWidgetWrapper *ppww = QgsGui::processingGuiRegistry()->createParameterWidgetWrapper( def, QgsProcessingGui::Standard );
mParametersLayout->addRow( ppww->createWrappedLabel(), ppww->createWrappedWidget( context ) );

QVariant value = method->parameterValues().value( def->name(), def->defaultValue() );
ppww->setParameterValue( value, context );

connect( ppww, &QgsAbstractProcessingParameterWidgetWrapper::widgetValueHasChanged, this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );

mParameterWidgetWrappers.push_back( std::unique_ptr<QgsAbstractProcessingParameterWidgetWrapper>( ppww ) );
Expand Down
11 changes: 7 additions & 4 deletions tests/src/python/test_qgsclassificationmethod.py
Expand Up @@ -71,17 +71,20 @@ def testQgsClassificationLogarithmic(self):

def testQgsClassificationLogarithmic_FilterZeroNeg(self):
values = [-2, 0, 1, 7, 66, 555, 4444]

vl = createMemoryLayer(values)

m = QgsClassificationLogarithmic()
m.setParameterValues({'FILTER_ZERO_NEG_VALUES': True})
r = m.classes(vl, 'value', 4)

m.setParameterValues({'ZERO_NEG_VALUES_HANDLE': QgsClassificationLogarithmic.Discard})
r = m.classes(vl, 'value', 4)
self.assertEqual(len(r), 4)
self.assertEqual(r[0].label(), '1 - 10^1')
self.assertEqual(QgsClassificationMethod.rangesToBreaks(r), [10.0, 100.0, 1000.0, 10000.0])

m.setParameterValues({'ZERO_NEG_VALUES_HANDLE': QgsClassificationLogarithmic.PrependBreak})
r = m.classes(vl, 'value', 4)
self.assertEqual(r[0].label(), '-2 - 10^0')
self.assertEqual(QgsClassificationMethod.rangesToBreaks(r), [1.0, 10.0, 100.0, 1000.0, 10000.0])


if __name__ == "__main__":
unittest.main()

0 comments on commit c5766dc

Please sign in to comment.