QGIS API Documentation  3.26.3-Buenos Aires (65e4edfdad)
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
6  email : [email protected]
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 
31 QList<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 
40 QgsClassificationMethod::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 
61 QgsClassificationMethod *QgsClassificationMethod::create( const QDomElement &element, const QgsReadWriteContext &context )
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 );
84  method->setLabelPrecision( precision );
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 
101 QDomElement 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 
135 void 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 
157 QString 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 
197 void 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 
209 QList<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 
251 QList<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 
264 QList<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 
278 QList<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 
301 void 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 
339 QString 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 }
QgsClassificationMethod
QgsClassificationMethod is an abstract class for implementations of classification methods.
Definition: qgsclassificationmethod.h:88
QgsReadWriteContext
The class is used as a container of context for various read/write operations on other objects.
Definition: qgsreadwritecontext.h:34
QgsVectorLayer::minimumAndMaximumValue
void minimumAndMaximumValue(int index, QVariant &minimum, QVariant &maximum) const
Calculates both the minimum and maximum value for an attribute column.
Definition: qgsvectorlayer.cpp:4384
QgsClassificationMethod::UpperBound
@ UpperBound
The class is at the upper bound.
Definition: qgsclassificationmethod.h:128
QgsClassificationMethod::formatNumber
QString formatNumber(double value) const
Format the number according to label properties.
Definition: qgsclassificationmethod.cpp:157
QgsClassificationMethod::create
static QgsClassificationMethod * create(const QDomElement &element, const QgsReadWriteContext &context)
Reads the DOM element and return a new classification method from it.
Definition: qgsclassificationmethod.cpp:61
QgsClassificationMethodRegistry::method
QgsClassificationMethod * method(const QString &id)
Returns a new instance of the method for the given id.
Definition: qgsclassificationmethodregistry.cpp:55
QgsClassificationMethod::writeXml
virtual void writeXml(QDomElement &element, const QgsReadWriteContext &context) const
Writes extra information about the method.
Definition: qgsclassificationmethod.h:169
QgsClassificationMethod::save
QDomElement save(QDomDocument &doc, const QgsReadWriteContext &context) const
Saves the method to a DOM element and return it.
Definition: qgsclassificationmethod.cpp:101
QgsRendererRange
Definition: qgsrendererrange.h:36
QgsClassificationMethod::labelForRange
virtual QString labelForRange(double lowerValue, double upperValue, ClassPosition position=Inner) const
Returns the label for a range.
Definition: qgsclassificationmethod.cpp:339
qgis.h
QgsClassificationMethod::classes
QList< QgsClassificationRange > classes(const QgsVectorLayer *layer, const QString &expression, int nclasses)
This will calculate the classes for a given layer to define the classes.
Definition: qgsclassificationmethod.cpp:209
QgsClassificationMethod::addParameter
void addParameter(QgsProcessingParameterDefinition *definition)
Add a parameter to the method.
Definition: qgsclassificationmethod.cpp:181
QgsProcessingParameterDefinition
Base class for the definition of processing parameters.
Definition: qgsprocessingparameters.h:334
QgsRendererRange::lowerValue
double lowerValue() const
Returns the lower bound of the range.
Definition: qgsrendererrange.cpp:74
QgsDebugMsg
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
QgsClassificationMethod::valuesRequired
virtual bool valuesRequired() const
Returns if the method requires values to calculate the classes If not, bounds are sufficient.
Definition: qgsclassificationmethod.h:177
QgsClassificationMethod::parameterDefinition
const QgsProcessingParameterDefinition * parameterDefinition(const QString &parameterName) const
Returns the parameter from its name.
Definition: qgsclassificationmethod.cpp:186
qgsapplication.h
QgsClassificationMethod::symmetryPoint
double symmetryPoint() const
Returns the symmetry point for symmetric mode.
Definition: qgsclassificationmethod.h:199
QgsVectorLayer::fields
QgsFields fields() const FINAL
Returns the list of fields of this layer.
Definition: qgsvectorlayer.cpp:3436
QgsClassificationMethod::LowerBound
@ LowerBound
The class is at the lower bound.
Definition: qgsclassificationmethod.h:126
QgsClassificationMethod::labelPrecision
int labelPrecision() const
Returns the precision for the formatting of the labels.
Definition: qgsclassificationmethod.h:222
precision
int precision
Definition: qgswfsgetfeature.cpp:103
QgsClassificationMethod::setLabelFormat
void setLabelFormat(const QString &format)
Defines the format of the labels for the classes, using %1 and %2 for the bounds.
Definition: qgsclassificationmethod.h:220
QgsClassificationMethod::setSymmetricMode
void setSymmetricMode(bool enabled, double symmetryPoint=0, bool symmetryAstride=false)
Defines if the symmetric mode is enables and configures its parameters.
Definition: qgsclassificationmethod.cpp:135
QgsClassificationMethod::MIN_PRECISION
static const int MIN_PRECISION
Definition: qgsclassificationmethod.h:311
QgsClassificationMethod::symmetricModeEnabled
bool symmetricModeEnabled() const
Returns if the symmetric mode is enabled.
Definition: qgsclassificationmethod.h:194
QgsXmlUtils::readVariant
static QVariant readVariant(const QDomElement &element)
Read a QVariant from a QDomElement.
Definition: qgsxmlutils.cpp:251
QgsClassificationMethod::MAX_PRECISION
static const int MAX_PRECISION
Definition: qgsclassificationmethod.h:310
QgsClassificationMethod::setLabelPrecision
void setLabelPrecision(int labelPrecision)
Defines the precision for the formatting of the labels.
Definition: qgsclassificationmethod.cpp:142
QgsClassificationMethod::labelTrimTrailingZeroes
bool labelTrimTrailingZeroes() const
Returns if the trailing 0 are trimmed in the label.
Definition: qgsclassificationmethod.h:226
QgsMessageLog::logMessage
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).
Definition: qgsmessagelog.cpp:27
qgsvectorlayerutils.h
QgsClassificationMethod::copyBase
void copyBase(QgsClassificationMethod *c) const
Copy the parameters (shall be used in clone implementation)
Definition: qgsclassificationmethod.cpp:52
QgsClassificationMethod::Inner
@ Inner
The class is not at a bound.
Definition: qgsclassificationmethod.h:127
QgsRendererRange::upperValue
double upperValue() const
Returns the upper bound of the range.
Definition: qgsrendererrange.cpp:79
QgsApplication::classificationMethodRegistry
static QgsClassificationMethodRegistry * classificationMethodRegistry()
Returns the application's classification methods registry, used in graduated renderer.
Definition: qgsapplication.cpp:2420
qgsxmlutils.h
qgsclassificationmethod.h
qgsvectorlayer.h
QgsClassificationMethod::readXml
virtual void readXml(const QDomElement &element, const QgsReadWriteContext &context)
Reads extra information to apply it to the method.
Definition: qgsclassificationmethod.h:171
QgsClassificationRange
QgsClassificationRange contains the information about a classification range.
Definition: qgsclassificationmethod.h:49
QgsClassificationMethod::labelFormat
QString labelFormat() const
Returns the format of the label for the classes.
Definition: qgsclassificationmethod.h:218
QgsClassificationMethod::makeBreaksSymmetric
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...
Definition: qgsclassificationmethod.cpp:301
QgsClassificationMethod::setParameterValues
void setParameterValues(const QVariantMap &values)
Defines the values of the additional parameters.
Definition: qgsclassificationmethod.cpp:197
c
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
Definition: porting_processing.dox:1
QgsVectorLayer
Represents a vector layer which manages a vector based data sets.
Definition: qgsvectorlayer.h:391
QgsClassificationMethod::ClassPosition
ClassPosition
Defines the class position.
Definition: qgsclassificationmethod.h:124
QgsClassificationMethod::setLabelTrimTrailingZeroes
void setLabelTrimTrailingZeroes(bool trimTrailingZeroes)
Defines if the trailing 0 are trimmed in the label.
Definition: qgsclassificationmethod.h:228
QgsClassificationMethod::QgsClassificationMethod
QgsClassificationMethod(MethodProperties properties=NoFlag, int codeComplexity=1)
Creates a classification method.
Definition: qgsclassificationmethod.cpp:40
qgsgraduatedsymbolrenderer.h
QgsXmlUtils::writeVariant
static QDomElement writeVariant(const QVariant &value, QDomDocument &doc)
Write a QVariant to a QDomElement.
Definition: qgsxmlutils.cpp:106
qgsclassificationmethodregistry.h
QgsClassificationMethod::rangesToBreaks
static QList< double > rangesToBreaks(const QList< QgsClassificationRange > &classes)
Transforms a list of classes to a list of breaks.
Definition: qgsclassificationmethod.cpp:31
QgsClassificationMethod::~QgsClassificationMethod
virtual ~QgsClassificationMethod()
Definition: qgsclassificationmethod.cpp:47
QgsClassificationMethod::name
virtual QString name() const =0
The readable and translate name of the method.
QgsClassificationMethod::parameterValues
QVariantMap parameterValues() const
Returns the values of the processing parameters.
Definition: qgsclassificationmethod.h:308
QgsFields::indexFromName
int indexFromName(const QString &fieldName) const
Gets the field index from the field name.
Definition: qgsfields.cpp:202
QgsVectorLayerUtils::getDoubleValues
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.
Definition: qgsvectorlayerutils.cpp:129