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