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