QGIS API Documentation 3.99.0-Master (2fe06baccd8)
Loading...
Searching...
No Matches
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
17
18#include "qgis.h"
19#include "qgsapplication.h"
22#include "qgsmessagelog.h"
23#include "qgsvectorlayer.h"
24#include "qgsvectorlayerutils.h"
25#include "qgsxmlutils.h"
26
27#include <QRegularExpression>
28
31
32
33QList<double> QgsClassificationMethod::rangesToBreaks( const QList<QgsClassificationRange> &classes )
34{
35 QList<double> values;
36 values.reserve( classes.count() );
37 for ( int i = 0 ; i < classes.count(); i++ )
38 values << classes.at( i ).upperBound();
39 return values;
40}
41
43 : mFlags( properties )
44 , mCodeComplexity( codeComplexity )
45 , mLabelFormat( QStringLiteral( "%1 - %2" ) )
46{
47}
48
50{
51 qDeleteAll( mParameters );
52}
53
55{
56 c->setSymmetricMode( mSymmetricEnabled, mSymmetryPoint, mSymmetryAstride );
57 c->setLabelFormat( mLabelFormat );
58 c->setLabelPrecision( mLabelPrecision );
59 c->setLabelTrimTrailingZeroes( mLabelTrimTrailingZeroes );
60 c->setParameterValues( mParameterValues );
61}
62
63std::unique_ptr< QgsClassificationMethod > QgsClassificationMethod::create( const QDomElement &element, const QgsReadWriteContext &context )
64{
65 const QString methodId = element.attribute( QStringLiteral( "id" ) );
66 std::unique_ptr< QgsClassificationMethod > method = QgsApplication::classificationMethodRegistry()->method( methodId );
67
68 // symmetric
69 QDomElement symmetricModeElem = element.firstChildElement( QStringLiteral( "symmetricMode" ) );
70 if ( !symmetricModeElem.isNull() )
71 {
72 bool symmetricEnabled = symmetricModeElem.attribute( QStringLiteral( "enabled" ) ).toInt() == 1;
73 double symmetricPoint = symmetricModeElem.attribute( QStringLiteral( "symmetrypoint" ) ).toDouble();
74 bool astride = symmetricModeElem.attribute( QStringLiteral( "astride" ) ).toInt() == 1;
75 method->setSymmetricMode( symmetricEnabled, symmetricPoint, astride );
76 }
77
78 // label format
79 QDomElement labelFormatElem = element.firstChildElement( QStringLiteral( "labelFormat" ) );
80 if ( !labelFormatElem.isNull() )
81 {
82 QString format = labelFormatElem.attribute( QStringLiteral( "format" ), "%1" + QStringLiteral( " - " ) + "%2" );
83 int precision = labelFormatElem.attribute( QStringLiteral( "labelprecision" ), QStringLiteral( "4" ) ).toInt();
84 bool trimTrailingZeroes = labelFormatElem.attribute( QStringLiteral( "trimtrailingzeroes" ), QStringLiteral( "false" ) ) == QLatin1String( "true" );
85 method->setLabelFormat( format );
86 method->setLabelPrecision( precision );
87 method->setLabelTrimTrailingZeroes( trimTrailingZeroes );
88 }
89
90 // parameters (processing parameters)
91 QDomElement parametersElem = element.firstChildElement( QStringLiteral( "parameters" ) );
92 const QVariantMap parameterValues = QgsXmlUtils::readVariant( parametersElem.firstChildElement() ).toMap();
93 method->setParameterValues( parameterValues );
94
95 // Read specific properties from the implementation
96 QDomElement extraElem = element.firstChildElement( QStringLiteral( "extraInformation" ) );
97 if ( !extraElem.isNull() )
98 method->readXml( extraElem, context );
99
100 return method;
101}
102
103QDomElement QgsClassificationMethod::save( QDomDocument &doc, const QgsReadWriteContext &context ) const
104{
105 QDomElement methodElem = doc.createElement( QStringLiteral( "classificationMethod" ) );
106
107 methodElem.setAttribute( QStringLiteral( "id" ), id() );
108
109 // symmetric
110 QDomElement symmetricModeElem = doc.createElement( QStringLiteral( "symmetricMode" ) );
111 symmetricModeElem.setAttribute( QStringLiteral( "enabled" ), symmetricModeEnabled() ? 1 : 0 );
112 symmetricModeElem.setAttribute( QStringLiteral( "symmetrypoint" ), symmetryPoint() );
113 symmetricModeElem.setAttribute( QStringLiteral( "astride" ), mSymmetryAstride ? 1 : 0 );
114 methodElem.appendChild( symmetricModeElem );
115
116 // label format
117 QDomElement labelFormatElem = doc.createElement( QStringLiteral( "labelFormat" ) );
118 labelFormatElem.setAttribute( QStringLiteral( "format" ), labelFormat() );
119 labelFormatElem.setAttribute( QStringLiteral( "labelprecision" ), labelPrecision() );
120 labelFormatElem.setAttribute( QStringLiteral( "trimtrailingzeroes" ), labelTrimTrailingZeroes() ? 1 : 0 );
121 methodElem.appendChild( labelFormatElem );
122
123 // parameters (processing parameters)
124 QDomElement parametersElem = doc.createElement( QStringLiteral( "parameters" ) );
125 parametersElem.appendChild( QgsXmlUtils::writeVariant( mParameterValues, doc ) );
126 methodElem.appendChild( parametersElem );
127
128 // extra information
129 QDomElement extraElem = doc.createElement( QStringLiteral( "extraInformation" ) );
130 writeXml( extraElem, context );
131 methodElem.appendChild( extraElem );
132
133 return methodElem;
134}
135
136
137void QgsClassificationMethod::setSymmetricMode( bool enabled, double symmetryPoint, bool astride )
138{
139 mSymmetricEnabled = enabled;
140 mSymmetryPoint = symmetryPoint;
141 mSymmetryAstride = astride;
142}
143
145{
146 // Limit the range of decimal places to a reasonable range
147 precision = std::clamp( precision, MIN_PRECISION, MAX_PRECISION );
148 mLabelPrecision = precision;
149 mLabelNumberScale = 1.0;
150 mLabelNumberSuffix.clear();
151 while ( precision < 0 )
152 {
153 precision++;
154 mLabelNumberScale /= 10.0;
155 mLabelNumberSuffix.append( '0' );
156 }
157}
158
159QString QgsClassificationMethod::formatNumber( double value ) const
160{
161 static const QRegularExpression RE_TRAILING_ZEROES = QRegularExpression( "[.,]?0*$" );
162 static const QRegularExpression RE_NEGATIVE_ZERO = QRegularExpression( "^\\-0(?:[.,]0*)?$" );
163 if ( mLabelPrecision > 0 )
164 {
165 QString valueStr = QLocale().toString( value, 'f', mLabelPrecision );
166 if ( mLabelTrimTrailingZeroes )
167 valueStr = valueStr.remove( RE_TRAILING_ZEROES );
168 if ( RE_NEGATIVE_ZERO.match( valueStr ).hasMatch() )
169 valueStr = valueStr.mid( 1 );
170 return valueStr;
171 }
172 else
173 {
174 QString valueStr = QLocale().toString( value * mLabelNumberScale, 'f', 0 );
175 if ( valueStr == QLatin1String( "-0" ) )
176 valueStr = '0';
177 if ( valueStr != QLatin1String( "0" ) )
178 valueStr = valueStr + mLabelNumberSuffix;
179 return valueStr;
180 }
181}
182
184{
185 mParameters.append( definition );
186}
187
189{
190 for ( const QgsProcessingParameterDefinition *def : mParameters )
191 {
192 if ( def->name() == parameterName )
193 return def;
194 }
195 QgsMessageLog::logMessage( QStringLiteral( "No parameter definition found for %1 in %2 method." ).arg( parameterName ).arg( name() ) );
196 return nullptr;
197}
198
199void QgsClassificationMethod::setParameterValues( const QVariantMap &values )
200{
201 mParameterValues = values;
202 for ( auto it = mParameterValues.constBegin(); it != mParameterValues.constEnd(); ++it )
203 {
204 if ( !parameterDefinition( it.key() ) )
205 {
206 QgsMessageLog::logMessage( name(), QObject::tr( "Parameter %1 does not exist in the method" ).arg( it.key() ) );
207 }
208 }
209}
210
211QList<QgsClassificationRange> QgsClassificationMethod::classes( const QgsVectorLayer *layer, const QString &expression, int nclasses )
212{
213 QString error;
214 return classesV2( layer, expression, nclasses, error );
215}
216
217QList<QgsClassificationRange> QgsClassificationMethod::classesV2( const QgsVectorLayer *layer, const QString &expression, int nclasses, QString &error )
218{
219 if ( expression.isEmpty() )
220 return QList<QgsClassificationRange>();
221
222 if ( nclasses < 1 )
223 nclasses = 1;
224
225 QList<double> values;
226 double minimum;
227 double maximum;
228
229
230 int fieldIndex = layer->fields().indexFromName( expression );
231
232 bool ok;
233 if ( valuesRequired() || fieldIndex == -1 )
234 {
235 values = QgsVectorLayerUtils::getDoubleValues( layer, expression, ok );
236 if ( !ok || values.isEmpty() )
237 return QList<QgsClassificationRange>();
238
239 auto result = std::minmax_element( values.begin(), values.end() );
240 minimum = *result.first;
241 maximum = *result.second;
242 }
243 else
244 {
245 QVariant minVal;
246 QVariant maxVal;
247 layer->minimumAndMaximumValue( fieldIndex, minVal, maxVal );
248 minimum = minVal.toDouble();
249 maximum = maxVal.toDouble();
250 }
251
252 // get the breaks, minimum and maximum might be updated by implementation
253 QList<double> breaks = calculateBreaks( minimum, maximum, values, nclasses, error );
254 breaks.insert( 0, minimum );
255 // create classes
256 return breaksToClasses( breaks );
257}
258
259QList<QgsClassificationRange> QgsClassificationMethod::classes( const QList<double> &values, int nclasses )
260{
261 auto result = std::minmax_element( values.begin(), values.end() );
262 double minimum = *result.first;
263 double maximum = *result.second;
264
265 // get the breaks
266 QString error;
267 QList<double> breaks = calculateBreaks( minimum, maximum, values, nclasses, error );
268 ( void )error;
269
270 breaks.insert( 0, minimum );
271 // create classes
272 return breaksToClasses( breaks );
273}
274
275QList<QgsClassificationRange> QgsClassificationMethod::classes( double minimum, double maximum, int nclasses )
276{
277 if ( valuesRequired() )
278 {
279 QgsDebugError( QStringLiteral( "The classification method %1 tries to calculate classes without values while they are required." ).arg( name() ) );
280 }
281
282 // get the breaks
283 QString error;
284 QList<double> breaks = calculateBreaks( minimum, maximum, QList<double>(), nclasses, error );
285 ( void )error;
286
287 breaks.insert( 0, minimum );
288 // create classes
289 return breaksToClasses( breaks );
290}
291
292QList<QgsClassificationRange> QgsClassificationMethod::breaksToClasses( const QList<double> &breaks ) const
293{
294 QList<QgsClassificationRange> classes;
295
296 for ( int i = 1; i < breaks.count(); i++ )
297 {
298
299 const double lowerValue = breaks.at( i - 1 );
300 const double upperValue = breaks.at( i );
301
302 ClassPosition pos = Inner;
303 if ( i == 1 )
304 pos = LowerBound;
305 else if ( i == breaks.count() - 1 )
306 pos = UpperBound;
307
308 QString label = labelForRange( lowerValue, upperValue, pos );
309 classes << QgsClassificationRange( label, lowerValue, upperValue );
310 }
311
312 return classes;
313}
314
315void QgsClassificationMethod::makeBreaksSymmetric( QList<double> &breaks, double symmetryPoint, bool astride )
316{
317 // remove the breaks that are above the existing opposite sign classes
318 // to keep colors symmetrically balanced around symmetryPoint
319 // if astride is true, remove the symmetryPoint break so that
320 // the 2 classes form only one
321
322 if ( breaks.count() < 2 )
323 return;
324
325 std::sort( breaks.begin(), breaks.end() );
326 // breaks contain the maximum of the distrib but not the minimum
327 double distBelowSymmetricValue = std::fabs( breaks[0] - symmetryPoint );
328 double distAboveSymmetricValue = std::fabs( breaks[ breaks.size() - 2 ] - symmetryPoint ) ;
329 double absMin = std::min( distAboveSymmetricValue, distBelowSymmetricValue );
330
331 // make symmetric
332 for ( int i = 0; i <= breaks.size() - 2; ++i )
333 {
334 // part after "absMin" is for doubles rounding issues
335 if ( std::fabs( breaks.at( i ) - symmetryPoint ) >= ( absMin - std::fabs( breaks[0] - breaks[1] ) / 100. ) )
336 {
337 breaks.removeAt( i );
338 --i;
339 }
340 }
341 // remove symmetry point
342 if ( astride )
343 {
344 breaks.removeAll( symmetryPoint );
345 }
346}
347
349{
350 return labelForRange( range.lowerValue(), range.upperValue(), position );
351}
352
353QString QgsClassificationMethod::labelForRange( const double lowerValue, const double upperValue, ClassPosition position ) const
354{
355 Q_UNUSED( position )
356
357 const QString lowerLabel = valueToLabel( lowerValue );
358 const QString upperLabel = valueToLabel( upperValue );
359
360 return labelFormat().replace( QLatin1String( "%1" ), lowerLabel ).replace( QLatin1String( "%2" ), upperLabel );
361}
static QgsClassificationMethodRegistry * classificationMethodRegistry()
Returns the application's classification methods registry, used in graduated renderer.
std::unique_ptr< QgsClassificationMethod > method(const QString &id)
Returns a new instance of the method for the given id.
double symmetryPoint() const
Returns the symmetry point for symmetric mode.
int codeComplexity() const
Code complexity as the exponent in Big O notation.
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...
static std::unique_ptr< QgsClassificationMethod > create(const QDomElement &element, const QgsReadWriteContext &context)
Reads the DOM element and return a new classification method from it.
QFlags< MethodProperty > MethodProperties
Q_DECL_DEPRECATED 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.
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.
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.
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.
QList< QgsClassificationRange > classesV2(const QgsVectorLayer *layer, const QString &expression, int nclasses, QString &error)
This will calculate the classes for a given layer to define the classes.
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.
Contains the information about a classification range.
Q_INVOKABLE int indexFromName(const QString &fieldName) const
Gets the field index from the field name.
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true, const char *file=__builtin_FILE(), const char *function=__builtin_FUNCTION(), int line=__builtin_LINE())
Adds a message to the log instance (and creates it if necessary).
Base class for the definition of processing parameters.
A container for the context for various read/write operations on objects.
Represents a value range for a QgsGraduatedSymbolRenderer.
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 dataset.
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 QgsDebugError(str)
Definition qgslogger.h:57