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