QGIS API Documentation 3.29.0-Master (53715c36dc)
qgsclassificationmethod.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsclassificationmethod.cpp
3 ---------------------
4 begin : September 2019
5 copyright : (C) 2019 by Denis Rouzaud
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16#include <QRegularExpression>
17
18#include "qgis.h"
20#include "qgsvectorlayerutils.h"
21#include "qgsvectorlayer.h"
23#include "qgsapplication.h"
25#include "qgsxmlutils.h"
26
29
30
31QList<double> QgsClassificationMethod::rangesToBreaks( const QList<QgsClassificationRange> &classes )
32{
33 QList<double> values;
34 values.reserve( classes.count() );
35 for ( int i = 0 ; i < classes.count(); i++ )
36 values << classes.at( i ).upperBound();
37 return values;
38}
39
40QgsClassificationMethod::QgsClassificationMethod( MethodProperties properties, int codeComplexity )
41 : mFlags( properties )
42 , mCodeComplexity( codeComplexity )
43 , mLabelFormat( QStringLiteral( "%1 - %2" ) )
44{
45}
46
48{
49 qDeleteAll( mParameters );
50}
51
53{
54 c->setSymmetricMode( mSymmetricEnabled, mSymmetryPoint, mSymmetryAstride );
55 c->setLabelFormat( mLabelFormat );
56 c->setLabelPrecision( mLabelPrecision );
57 c->setLabelTrimTrailingZeroes( mLabelTrimTrailingZeroes );
58 c->setParameterValues( mParameterValues );
59}
60
62{
63 const QString methodId = element.attribute( QStringLiteral( "id" ) );
65
66 // symmetric
67 QDomElement symmetricModeElem = element.firstChildElement( QStringLiteral( "symmetricMode" ) );
68 if ( !symmetricModeElem.isNull() )
69 {
70 bool symmetricEnabled = symmetricModeElem.attribute( QStringLiteral( "enabled" ) ).toInt() == 1;
71 double symmetricPoint = symmetricModeElem.attribute( QStringLiteral( "symmetrypoint" ) ).toDouble();
72 bool astride = symmetricModeElem.attribute( QStringLiteral( "astride" ) ).toInt() == 1;
73 method->setSymmetricMode( symmetricEnabled, symmetricPoint, astride );
74 }
75
76 // label format
77 QDomElement labelFormatElem = element.firstChildElement( QStringLiteral( "labelformat" ) );
78 if ( !labelFormatElem.isNull() )
79 {
80 QString format = labelFormatElem.attribute( QStringLiteral( "format" ), "%1" + QStringLiteral( " - " ) + "%2" );
81 int precision = labelFormatElem.attribute( QStringLiteral( "labelprecision" ), QStringLiteral( "4" ) ).toInt();
82 bool trimTrailingZeroes = labelFormatElem.attribute( QStringLiteral( "trimtrailingzeroes" ), QStringLiteral( "false" ) ) == QLatin1String( "true" );
83 method->setLabelFormat( format );
85 method->setLabelTrimTrailingZeroes( trimTrailingZeroes );
86 }
87
88 // parameters (processing parameters)
89 QDomElement parametersElem = element.firstChildElement( QStringLiteral( "parameters" ) );
90 const QVariantMap parameterValues = QgsXmlUtils::readVariant( parametersElem.firstChildElement() ).toMap();
92
93 // Read specific properties from the implementation
94 QDomElement extraElem = element.firstChildElement( QStringLiteral( "extraInformation" ) );
95 if ( !extraElem.isNull() )
96 method->readXml( extraElem, context );
97
98 return method;
99}
100
101QDomElement QgsClassificationMethod::save( QDomDocument &doc, const QgsReadWriteContext &context ) const
102{
103 QDomElement methodElem = doc.createElement( QStringLiteral( "classificationMethod" ) );
104
105 methodElem.setAttribute( QStringLiteral( "id" ), id() );
106
107 // symmetric
108 QDomElement symmetricModeElem = doc.createElement( QStringLiteral( "symmetricMode" ) );
109 symmetricModeElem.setAttribute( QStringLiteral( "enabled" ), symmetricModeEnabled() ? 1 : 0 );
110 symmetricModeElem.setAttribute( QStringLiteral( "symmetrypoint" ), symmetryPoint() );
111 symmetricModeElem.setAttribute( QStringLiteral( "astride" ), mSymmetryAstride ? 1 : 0 );
112 methodElem.appendChild( symmetricModeElem );
113
114 // label format
115 QDomElement labelFormatElem = doc.createElement( QStringLiteral( "labelFormat" ) );
116 labelFormatElem.setAttribute( QStringLiteral( "format" ), labelFormat() );
117 labelFormatElem.setAttribute( QStringLiteral( "labelprecision" ), labelPrecision() );
118 labelFormatElem.setAttribute( QStringLiteral( "trimtrailingzeroes" ), labelTrimTrailingZeroes() ? 1 : 0 );
119 methodElem.appendChild( labelFormatElem );
120
121 // parameters (processing parameters)
122 QDomElement parametersElem = doc.createElement( QStringLiteral( "parameters" ) );
123 parametersElem.appendChild( QgsXmlUtils::writeVariant( mParameterValues, doc ) );
124 methodElem.appendChild( parametersElem );
125
126 // extra information
127 QDomElement extraElem = doc.createElement( QStringLiteral( "extraInformation" ) );
128 writeXml( extraElem, context );
129 methodElem.appendChild( extraElem );
130
131 return methodElem;
132}
133
134
135void QgsClassificationMethod::setSymmetricMode( bool enabled, double symmetryPoint, bool astride )
136{
137 mSymmetricEnabled = enabled;
138 mSymmetryPoint = symmetryPoint;
139 mSymmetryAstride = astride;
140}
141
143{
144 // Limit the range of decimal places to a reasonable range
146 mLabelPrecision = precision;
147 mLabelNumberScale = 1.0;
148 mLabelNumberSuffix.clear();
149 while ( precision < 0 )
150 {
151 precision++;
152 mLabelNumberScale /= 10.0;
153 mLabelNumberSuffix.append( '0' );
154 }
155}
156
157QString QgsClassificationMethod::formatNumber( double value ) const
158{
159 static const QRegularExpression RE_TRAILING_ZEROES = QRegularExpression( "[.,]?0*$" );
160 static const QRegularExpression RE_NEGATIVE_ZERO = QRegularExpression( "^\\-0(?:[.,]0*)?$" );
161 if ( mLabelPrecision > 0 )
162 {
163 QString valueStr = QLocale().toString( value, 'f', mLabelPrecision );
164 if ( mLabelTrimTrailingZeroes )
165 valueStr = valueStr.remove( RE_TRAILING_ZEROES );
166 if ( RE_NEGATIVE_ZERO.match( valueStr ).hasMatch() )
167 valueStr = valueStr.mid( 1 );
168 return valueStr;
169 }
170 else
171 {
172 QString valueStr = QLocale().toString( value * mLabelNumberScale, 'f', 0 );
173 if ( valueStr == QLatin1String( "-0" ) )
174 valueStr = '0';
175 if ( valueStr != QLatin1String( "0" ) )
176 valueStr = valueStr + mLabelNumberSuffix;
177 return valueStr;
178 }
179}
180
182{
183 mParameters.append( definition );
184}
185
187{
188 for ( const QgsProcessingParameterDefinition *def : mParameters )
189 {
190 if ( def->name() == parameterName )
191 return def;
192 }
193 QgsMessageLog::logMessage( QStringLiteral( "No parameter definition found for %1 in %2 method." ).arg( parameterName ).arg( name() ) );
194 return nullptr;
195}
196
197void QgsClassificationMethod::setParameterValues( const QVariantMap &values )
198{
199 mParameterValues = values;
200 for ( auto it = mParameterValues.constBegin(); it != mParameterValues.constEnd(); ++it )
201 {
202 if ( !parameterDefinition( it.key() ) )
203 {
204 QgsMessageLog::logMessage( name(), QObject::tr( "Parameter %1 does not exist in the method" ).arg( it.key() ) );
205 }
206 }
207}
208
209QList<QgsClassificationRange> QgsClassificationMethod::classes( const QgsVectorLayer *layer, const QString &expression, int nclasses )
210{
211 if ( expression.isEmpty() )
212 return QList<QgsClassificationRange>();
213
214 if ( nclasses < 1 )
215 nclasses = 1;
216
217 QList<double> values;
218 double minimum;
219 double maximum;
220
221
222 int fieldIndex = layer->fields().indexFromName( expression );
223
224 bool ok;
225 if ( valuesRequired() || fieldIndex == -1 )
226 {
227 values = QgsVectorLayerUtils::getDoubleValues( layer, expression, ok );
228 if ( !ok || values.isEmpty() )
229 return QList<QgsClassificationRange>();
230
231 auto result = std::minmax_element( values.begin(), values.end() );
232 minimum = *result.first;
233 maximum = *result.second;
234 }
235 else
236 {
237 QVariant minVal;
238 QVariant maxVal;
239 layer->minimumAndMaximumValue( fieldIndex, minVal, maxVal );
240 minimum = minVal.toDouble();
241 maximum = maxVal.toDouble();
242 }
243
244 // get the breaks, minimum and maximum might be updated by implementation
245 QList<double> breaks = calculateBreaks( minimum, maximum, values, nclasses );
246 breaks.insert( 0, minimum );
247 // create classes
248 return breaksToClasses( breaks );
249}
250
251QList<QgsClassificationRange> QgsClassificationMethod::classes( const QList<double> &values, int nclasses )
252{
253 auto result = std::minmax_element( values.begin(), values.end() );
254 double minimum = *result.first;
255 double maximum = *result.second;
256
257 // get the breaks
258 QList<double> breaks = calculateBreaks( minimum, maximum, values, nclasses );
259 breaks.insert( 0, minimum );
260 // create classes
261 return breaksToClasses( breaks );
262}
263
264QList<QgsClassificationRange> QgsClassificationMethod::classes( double minimum, double maximum, int nclasses )
265{
266 if ( valuesRequired() )
267 {
268 QgsDebugMsg( QStringLiteral( "The classification method %1 tries to calculate classes without values while they are required." ).arg( name() ) );
269 }
270
271 // get the breaks
272 QList<double> breaks = calculateBreaks( minimum, maximum, QList<double>(), nclasses );
273 breaks.insert( 0, minimum );
274 // create classes
275 return breaksToClasses( breaks );
276}
277
278QList<QgsClassificationRange> QgsClassificationMethod::breaksToClasses( const QList<double> &breaks ) const
279{
280 QList<QgsClassificationRange> classes;
281
282 for ( int i = 1; i < breaks.count(); i++ )
283 {
284
285 const double lowerValue = breaks.at( i - 1 );
286 const double upperValue = breaks.at( i );
287
288 ClassPosition pos = Inner;
289 if ( i == 1 )
290 pos = LowerBound;
291 else if ( i == breaks.count() - 1 )
292 pos = UpperBound;
293
294 QString label = labelForRange( lowerValue, upperValue, pos );
295 classes << QgsClassificationRange( label, lowerValue, upperValue );
296 }
297
298 return classes;
299}
300
301void QgsClassificationMethod::makeBreaksSymmetric( QList<double> &breaks, double symmetryPoint, bool astride )
302{
303 // remove the breaks that are above the existing opposite sign classes
304 // to keep colors symmetrically balanced around symmetryPoint
305 // if astride is true, remove the symmetryPoint break so that
306 // the 2 classes form only one
307
308 if ( breaks.count() < 2 )
309 return;
310
311 std::sort( breaks.begin(), breaks.end() );
312 // breaks contain the maximum of the distrib but not the minimum
313 double distBelowSymmetricValue = std::fabs( breaks[0] - symmetryPoint );
314 double distAboveSymmetricValue = std::fabs( breaks[ breaks.size() - 2 ] - symmetryPoint ) ;
315 double absMin = std::min( distAboveSymmetricValue, distBelowSymmetricValue );
316
317 // make symmetric
318 for ( int i = 0; i <= breaks.size() - 2; ++i )
319 {
320 // part after "absMin" is for doubles rounding issues
321 if ( std::fabs( breaks.at( i ) - symmetryPoint ) >= ( absMin - std::fabs( breaks[0] - breaks[1] ) / 100. ) )
322 {
323 breaks.removeAt( i );
324 --i;
325 }
326 }
327 // remove symmetry point
328 if ( astride )
329 {
330 breaks.removeAll( symmetryPoint );
331 }
332}
333
335{
336 return labelForRange( range.lowerValue(), range.upperValue(), position );
337}
338
339QString QgsClassificationMethod::labelForRange( const double lowerValue, const double upperValue, ClassPosition position ) const
340{
341 Q_UNUSED( position )
342
343 const QString lowerLabel = valueToLabel( lowerValue );
344 const QString upperLabel = valueToLabel( upperValue );
345
346 return labelFormat().arg( lowerLabel, upperLabel );
347}
static QgsClassificationMethodRegistry * classificationMethodRegistry()
Returns the application's classification methods registry, used in graduated renderer.
QgsClassificationMethod * method(const QString &id)
Returns a new instance of the method for the given id.
QgsClassificationMethod is an abstract class for implementations of classification methods.
double symmetryPoint() const
Returns the symmetry point for symmetric mode.
bool symmetricModeEnabled() const
Returns if the symmetric mode is enabled.
static void makeBreaksSymmetric(QList< double > &breaks, double symmetryPoint, bool astride)
Remove the breaks that are above the existing opposite sign classes to keep colors symmetrically bala...
QList< QgsClassificationRange > classes(const QgsVectorLayer *layer, const QString &expression, int nclasses)
This will calculate the classes for a given layer to define the classes.
int labelPrecision() const
Returns the precision for the formatting of the labels.
void setLabelTrimTrailingZeroes(bool trimTrailingZeroes)
Defines if the trailing 0 are trimmed in the label.
QVariantMap parameterValues() const
Returns the values of the processing parameters.
void setSymmetricMode(bool enabled, double symmetryPoint=0, bool symmetryAstride=false)
Defines if the symmetric mode is enables and configures its parameters.
ClassPosition
Defines the class position.
@ LowerBound
The class is at the lower bound.
@ UpperBound
The class is at the upper bound.
@ Inner
The class is not at a bound.
void addParameter(QgsProcessingParameterDefinition *definition)
Add a parameter to the method.
virtual void readXml(const QDomElement &element, const QgsReadWriteContext &context)
Reads extra information to apply it to the method.
static QList< double > rangesToBreaks(const QList< QgsClassificationRange > &classes)
Transforms a list of classes to a list of breaks.
virtual void writeXml(QDomElement &element, const QgsReadWriteContext &context) const
Writes extra information about the method.
QString labelFormat() const
Returns the format of the label for the classes.
virtual bool valuesRequired() const
Returns if the method requires values to calculate the classes If not, bounds are sufficient.
void setParameterValues(const QVariantMap &values)
Defines the values of the additional parameters.
virtual QString name() const =0
The readable and translate name of the method.
QgsClassificationMethod(MethodProperties properties=NoFlag, int codeComplexity=1)
Creates a classification method.
void setLabelFormat(const QString &format)
Defines the format of the labels for the classes, using %1 and %2 for the bounds.
static QgsClassificationMethod * create(const QDomElement &element, const QgsReadWriteContext &context)
Reads the DOM element and return a new classification method from it.
const QgsProcessingParameterDefinition * parameterDefinition(const QString &parameterName) const
Returns the parameter from its name.
QString formatNumber(double value) const
Format the number according to label properties.
bool labelTrimTrailingZeroes() const
Returns if the trailing 0 are trimmed in the label.
void setLabelPrecision(int labelPrecision)
Defines the precision for the formatting of the labels.
void copyBase(QgsClassificationMethod *c) const
Copy the parameters (shall be used in clone implementation)
virtual QString labelForRange(double lowerValue, double upperValue, ClassPosition position=Inner) const
Returns the label for a range.
QDomElement save(QDomDocument &doc, const QgsReadWriteContext &context) const
Saves the method to a DOM element and return it.
QgsClassificationRange contains the information about a classification range.
int indexFromName(const QString &fieldName) const
Gets the field index from the field name.
Definition: qgsfields.cpp:202
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
Base class for the definition of processing parameters.
The class is used as a container of context for various read/write operations on other objects.
double upperValue() const
Returns the upper bound of the range.
double lowerValue() const
Returns the lower bound of the range.
static QList< double > getDoubleValues(const QgsVectorLayer *layer, const QString &fieldOrExpression, bool &ok, bool selectedOnly=false, int *nullCount=nullptr, QgsFeedback *feedback=nullptr)
Fetches all double values from a specified field name or expression.
Represents a vector layer which manages a vector based data sets.
QgsFields fields() const FINAL
Returns the list of fields of this layer.
void minimumAndMaximumValue(int index, QVariant &minimum, QVariant &maximum) const
Calculates both the minimum and maximum value for an attribute column.
static QDomElement writeVariant(const QVariant &value, QDomDocument &doc)
Write a QVariant to a QDomElement.
static QVariant readVariant(const QDomElement &element)
Read a QVariant from a QDomElement.
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
int precision