QGIS API Documentation  3.10.0-A Coruña (6c816b4204)
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 
28 
29 static const QRegularExpression RE_TRAILING_ZEROES = QRegularExpression( "[.,]?0*$" );
30 static const QRegularExpression RE_NEGATIVE_ZERO = QRegularExpression( "^\\-0(?:[.,]0*)?$" );
31 
32 QList<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  c->setSymmetricMode( mSymmetricEnabled, mSymmetryPoint, mSymmetryAstride );
51  c->setLabelFormat( mLabelFormat );
52  c->setLabelPrecision( mLabelPrecision );
53  c->setLabelTrimTrailingZeroes( mLabelTrimTrailingZeroes );
54 }
55 
56 QgsClassificationMethod *QgsClassificationMethod::create( const QDomElement &element, const QgsReadWriteContext &context )
57 {
58  const QString methodId = element.attribute( QStringLiteral( "id" ) );
60 
61  // symmetric
62  QDomElement symmetricModeElem = element.firstChildElement( QStringLiteral( "symmetricMode" ) );
63  if ( !symmetricModeElem.isNull() )
64  {
65  bool symmetricEnabled = symmetricModeElem.attribute( QStringLiteral( "enabled" ) ).toInt() == 1;
66  double symmetricPoint = symmetricModeElem.attribute( QStringLiteral( "symmetrypoint" ) ).toDouble();
67  bool astride = symmetricModeElem.attribute( QStringLiteral( "astride" ) ).toInt() == 1;
68  method->setSymmetricMode( symmetricEnabled, symmetricPoint, astride );
69  }
70 
71  // label format
72  QDomElement labelFormatElem = element.firstChildElement( QStringLiteral( "labelformat" ) );
73  if ( !labelFormatElem.isNull() )
74  {
75  QString format = labelFormatElem.attribute( QStringLiteral( "format" ), "%1" + QStringLiteral( " - " ) + "%2" );
76  int precision = labelFormatElem.attribute( QStringLiteral( "labelprecision" ), QStringLiteral( "4" ) ).toInt();
77  bool trimTrailingZeroes = labelFormatElem.attribute( QStringLiteral( "trimtrailingzeroes" ), QStringLiteral( "false" ) ) == QLatin1String( "true" );
78  method->setLabelFormat( format );
79  method->setLabelPrecision( precision );
80  method->setLabelTrimTrailingZeroes( trimTrailingZeroes );
81  }
82 
83  // Read specific properties from the implementation
84  QDomElement extraElem = element.firstChildElement( QStringLiteral( "extraInformation" ) );
85  if ( !extraElem.isNull() )
86  method->readXml( extraElem, context );
87 
88  return method;
89 }
90 
91 QDomElement QgsClassificationMethod::save( QDomDocument &doc, const QgsReadWriteContext &context ) const
92 {
93  QDomElement methodElem = doc.createElement( QStringLiteral( "classificationMethod" ) );
94 
95  methodElem.setAttribute( QStringLiteral( "id" ), id() );
96 
97  // symmetric
98  QDomElement symmetricModeElem = doc.createElement( QStringLiteral( "symmetricMode" ) );
99  symmetricModeElem.setAttribute( QStringLiteral( "enabled" ), symmetricModeEnabled() ? 1 : 0 );
100  symmetricModeElem.setAttribute( QStringLiteral( "symmetrypoint" ), symmetryPoint() );
101  symmetricModeElem.setAttribute( QStringLiteral( "astride" ), mSymmetryAstride ? 1 : 0 );
102  methodElem.appendChild( symmetricModeElem );
103 
104  // label format
105  QDomElement labelFormatElem = doc.createElement( QStringLiteral( "labelFormat" ) );
106  labelFormatElem.setAttribute( QStringLiteral( "format" ), labelFormat() );
107  labelFormatElem.setAttribute( QStringLiteral( "labelprecision" ), labelPrecision() );
108  labelFormatElem.setAttribute( QStringLiteral( "trimtrailingzeroes" ), labelTrimTrailingZeroes() ? 1 : 0 );
109  methodElem.appendChild( labelFormatElem );
110 
111  // extra information
112  QDomElement extraElem = doc.createElement( QStringLiteral( "extraInformation" ) );
113  writeXml( extraElem, context );
114  methodElem.appendChild( extraElem );
115 
116  return methodElem;
117 }
118 
119 
120 void QgsClassificationMethod::setSymmetricMode( bool enabled, double symmetryPoint, bool astride )
121 {
122  mSymmetricEnabled = enabled;
123  mSymmetryPoint = symmetryPoint;
124  mSymmetryAstride = astride;
125 }
126 
128 {
129  // Limit the range of decimal places to a reasonable range
130  precision = qBound( MIN_PRECISION, precision, MAX_PRECISION );
131  mLabelPrecision = precision;
132  mLabelNumberScale = 1.0;
133  mLabelNumberSuffix.clear();
134  while ( precision < 0 )
135  {
136  precision++;
137  mLabelNumberScale /= 10.0;
138  mLabelNumberSuffix.append( '0' );
139  }
140 }
141 
142 QString QgsClassificationMethod::formatNumber( double value ) const
143 {
144  if ( mLabelPrecision > 0 )
145  {
146  QString valueStr = QLocale().toString( value, 'f', mLabelPrecision );
147  if ( mLabelTrimTrailingZeroes )
148  valueStr = valueStr.remove( RE_TRAILING_ZEROES );
149  if ( RE_NEGATIVE_ZERO.match( valueStr ).hasMatch() )
150  valueStr = valueStr.mid( 1 );
151  return valueStr;
152  }
153  else
154  {
155  QString valueStr = QLocale().toString( value * mLabelNumberScale, 'f', 0 );
156  if ( valueStr == QLatin1String( "-0" ) )
157  valueStr = '0';
158  if ( valueStr != QLatin1String( "0" ) )
159  valueStr = valueStr + mLabelNumberSuffix;
160  return valueStr;
161  }
162 }
163 
164 QList<QgsClassificationRange> QgsClassificationMethod::classes( const QgsVectorLayer *layer, const QString &expression, int nclasses )
165 {
166  if ( expression.isEmpty() )
167  return QList<QgsClassificationRange>();
168 
169  if ( nclasses < 1 )
170  nclasses = 1;
171 
172  QList<double> values;
173  double minimum;
174  double maximum;
175 
176  int fieldIndex = layer->fields().indexFromName( expression );
177 
178  bool ok;
179  if ( valuesRequired() || fieldIndex == -1 )
180  {
181  values = QgsVectorLayerUtils::getDoubleValues( layer, expression, ok );
182  if ( !ok || values.isEmpty() )
183  return QList<QgsClassificationRange>();
184 
185  auto result = std::minmax_element( values.begin(), values.end() );
186  minimum = *result.first;
187  maximum = *result.second;
188  }
189  else
190  {
191  minimum = layer->minimumValue( fieldIndex ).toDouble();
192  maximum = layer->maximumValue( fieldIndex ).toDouble();
193  }
194 
195  // get the breaks
196  QList<double> breaks = calculateBreaks( minimum, maximum, values, nclasses );
197  breaks.insert( 0, minimum );
198  // create classes
199  return breaksToClasses( breaks );
200 }
201 
202 QList<QgsClassificationRange> QgsClassificationMethod::classes( const QList<double> &values, int nclasses )
203 {
204  auto result = std::minmax_element( values.begin(), values.end() );
205  double minimum = *result.first;
206  double maximum = *result.second;
207 
208  // get the breaks
209  QList<double> breaks = calculateBreaks( minimum, maximum, values, nclasses );
210  breaks.insert( 0, minimum );
211  // create classes
212  return breaksToClasses( breaks );
213 }
214 
215 QList<QgsClassificationRange> QgsClassificationMethod::classes( double minimum, double maximum, int nclasses )
216 {
217  if ( valuesRequired() )
218  {
219  QgsDebugMsg( QStringLiteral( "The classification method %1 tries to calculate classes without values while they are required." ).arg( name() ) );
220  }
221 
222  // get the breaks
223  QList<double> breaks = calculateBreaks( minimum, maximum, QList<double>(), nclasses );
224  breaks.insert( 0, minimum );
225  // create classes
226  return breaksToClasses( breaks );
227 }
228 
229 QList<QgsClassificationRange> QgsClassificationMethod::breaksToClasses( const QList<double> &breaks ) const
230 {
231  QList<QgsClassificationRange> classes;
232 
233  for ( int i = 1; i < breaks.count(); i++ )
234  {
235 
236  const double lowerValue = breaks.at( i - 1 );
237  const double upperValue = breaks.at( i );
238 
239  ClassPosition pos = Inner;
240  if ( i == 1 )
241  pos = LowerBound;
242  else if ( i == breaks.count() - 1 )
243  pos = UpperBound;
244 
245  QString label = labelForRange( lowerValue, upperValue, pos );
246  classes << QgsClassificationRange( label, lowerValue, upperValue );
247  }
248 
249  return classes;
250 }
251 
252 void QgsClassificationMethod::makeBreaksSymmetric( QList<double> &breaks, double symmetryPoint, bool astride )
253 {
254  // remove the breaks that are above the existing opposite sign classes
255  // to keep colors symmetrically balanced around symmetryPoint
256  // if astride is true, remove the symmetryPoint break so that
257  // the 2 classes form only one
258 
259  if ( breaks.count() < 2 )
260  return;
261 
262  std::sort( breaks.begin(), breaks.end() );
263  // breaks contain the maximum of the distrib but not the minimum
264  double distBelowSymmetricValue = std::fabs( breaks[0] - symmetryPoint );
265  double distAboveSymmetricValue = std::fabs( breaks[ breaks.size() - 2 ] - symmetryPoint ) ;
266  double absMin = std::min( distAboveSymmetricValue, distBelowSymmetricValue );
267 
268  // make symmetric
269  for ( int i = 0; i <= breaks.size() - 2; ++i )
270  {
271  // part after "absMin" is for doubles rounding issues
272  if ( std::fabs( breaks.at( i ) - symmetryPoint ) >= ( absMin - std::fabs( breaks[0] - breaks[1] ) / 100. ) )
273  {
274  breaks.removeAt( i );
275  --i;
276  }
277  }
278  // remove symmetry point
279  if ( astride ) // && breaks.indexOf( symmetryPoint ) != -1) // if symmetryPoint is found
280  {
281  breaks.removeAt( breaks.indexOf( symmetryPoint ) );
282  }
283 }
284 
286 {
287  return labelForRange( range.lowerValue(), range.upperValue(), position );
288 }
289 
290 QString QgsClassificationMethod::labelForRange( const double lowerValue, const double upperValue, ClassPosition position ) const
291 {
292  Q_UNUSED( position )
293 
294  const QString lowerLabel = valueToLabel( lowerValue );
295  const QString upperLabel = valueToLabel( upperValue );
296 
297  return labelFormat().arg( lowerLabel ).arg( upperLabel );
298 }
QgsClassificationMethod * method(const QString &id)
Returns a new instance of the method for the given id.
The class is used as a container of context for various read/write operations on other objects...
int precision
virtual QString labelForRange(double lowerValue, double upperValue, ClassPosition position=Inner) const
Returns the label for a range.
QList< QgsClassificationRange > classes(const QgsVectorLayer *layer, const QString &expression, int nclasses)
This will calculate the classes for a given layer to define the classes.
QString formatNumber(double value) const
Format the number according to label properties.
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
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.
int codeComplexity() const
Code complexity as the exponent in Big O notation.
QgsClassificationRange contains the information about a classification range.
virtual void writeXml(QDomElement &element, const QgsReadWriteContext &context) const
Writes extra information about the method.
The class is not at a bound.
bool valuesRequired() const
Returns if the method requires values to calculate the classes If not, bounds are sufficient...
QDomElement save(QDomDocument &doc, const QgsReadWriteContext &context) const
Saves the method to a DOM element and return it.
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
double lowerValue() const
The class is at the upper bound.
void setSymmetricMode(bool enabled, double symmetryPoint=0, bool symmetryAstride=false)
Defines if the symmetric mode is enables and configures its parameters.
The class is at the lower bound.
int indexFromName(const QString &fieldName) const
Gets the field index from the field name.
Definition: qgsfields.cpp:202
QgsFields fields() const FINAL
Returns the list of fields of this layer.
double symmetryPoint() const
Returns the symmetry point for symmetric mode.
int labelPrecision() const
Returns the precision for the formatting of the labels.
void setLabelFormat(const QString &format)
Defines the format of the labels for the classes, using %1 and %2 for the bounds. ...
bool labelTrimTrailingZeroes() const
Returns if the trailing 0 are trimmed in the label.
QVariant minimumValue(int index) const FINAL
Returns the minimum value for an attribute column or an invalid variant in case of error...
void setLabelPrecision(int labelPrecision)
Defines the precision for the formatting of the labels.
void copyBase(QgsClassificationMethod *c) const
Copy the parameters (shall be used in clone implementation)
bool symmetricModeEnabled() const
Returns if the symmetric mode is enabled.
virtual void readXml(const QDomElement &element, const QgsReadWriteContext &context)
Reads extra information to apply it to the method.
QString labelFormat() const
Returns the format of the label for the classes.
QgsClassificationMethod(MethodProperties properties=NoFlag, int codeComplexity=1)
Creates a classification method.
static QgsClassificationMethodRegistry * classificationMethodRegistry()
Returns the application&#39;s classification methods registry, used in graduated renderer.
static QList< double > rangesToBreaks(const QList< QgsClassificationRange > &classes)
Transforms a list of classes to a list of breaks.
double upperValue() const
ClassPosition
Defines the class position.
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...
void setLabelTrimTrailingZeroes(bool trimTrailingZeroes)
Defines if the trailing 0 are trimmed in the label.
QVariant maximumValue(int index) const FINAL
Returns the maximum value for an attribute column or an invalid variant in case of error...
Represents a vector layer which manages a vector based data sets.
QgsClassificationMethod is an abstract class for implementations of classification methods...
virtual QString name() const =0
The readable and translate name of the method.
static QgsClassificationMethod * create(const QDomElement &element, const QgsReadWriteContext &context)
Reads the DOM element and return a new classification method from it.