QGIS API Documentation  3.16.0-Hannover (43b64b13f3)
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  minimum = layer->minimumValue( fieldIndex ).toDouble();
238  maximum = layer->maximumValue( fieldIndex ).toDouble();
239  }
240 
241  // get the breaks, minimum and maximum might be updated by implementation
242  QList<double> breaks = calculateBreaks( minimum, maximum, values, nclasses );
243  breaks.insert( 0, minimum );
244  // create classes
245  return breaksToClasses( breaks );
246 }
247 
248 QList<QgsClassificationRange> QgsClassificationMethod::classes( const QList<double> &values, int nclasses )
249 {
250  auto result = std::minmax_element( values.begin(), values.end() );
251  double minimum = *result.first;
252  double maximum = *result.second;
253 
254  // get the breaks
255  QList<double> breaks = calculateBreaks( minimum, maximum, values, nclasses );
256  breaks.insert( 0, minimum );
257  // create classes
258  return breaksToClasses( breaks );
259 }
260 
261 QList<QgsClassificationRange> QgsClassificationMethod::classes( double minimum, double maximum, int nclasses )
262 {
263  if ( valuesRequired() )
264  {
265  QgsDebugMsg( QStringLiteral( "The classification method %1 tries to calculate classes without values while they are required." ).arg( name() ) );
266  }
267 
268  // get the breaks
269  QList<double> breaks = calculateBreaks( minimum, maximum, QList<double>(), nclasses );
270  breaks.insert( 0, minimum );
271  // create classes
272  return breaksToClasses( breaks );
273 }
274 
275 QList<QgsClassificationRange> QgsClassificationMethod::breaksToClasses( const QList<double> &breaks ) const
276 {
277  QList<QgsClassificationRange> classes;
278 
279  for ( int i = 1; i < breaks.count(); i++ )
280  {
281 
282  const double lowerValue = breaks.at( i - 1 );
283  const double upperValue = breaks.at( i );
284 
285  ClassPosition pos = Inner;
286  if ( i == 1 )
287  pos = LowerBound;
288  else if ( i == breaks.count() - 1 )
289  pos = UpperBound;
290 
291  QString label = labelForRange( lowerValue, upperValue, pos );
292  classes << QgsClassificationRange( label, lowerValue, upperValue );
293  }
294 
295  return classes;
296 }
297 
298 void QgsClassificationMethod::makeBreaksSymmetric( QList<double> &breaks, double symmetryPoint, bool astride )
299 {
300  // remove the breaks that are above the existing opposite sign classes
301  // to keep colors symmetrically balanced around symmetryPoint
302  // if astride is true, remove the symmetryPoint break so that
303  // the 2 classes form only one
304 
305  if ( breaks.count() < 2 )
306  return;
307 
308  std::sort( breaks.begin(), breaks.end() );
309  // breaks contain the maximum of the distrib but not the minimum
310  double distBelowSymmetricValue = std::fabs( breaks[0] - symmetryPoint );
311  double distAboveSymmetricValue = std::fabs( breaks[ breaks.size() - 2 ] - symmetryPoint ) ;
312  double absMin = std::min( distAboveSymmetricValue, distBelowSymmetricValue );
313 
314  // make symmetric
315  for ( int i = 0; i <= breaks.size() - 2; ++i )
316  {
317  // part after "absMin" is for doubles rounding issues
318  if ( std::fabs( breaks.at( i ) - symmetryPoint ) >= ( absMin - std::fabs( breaks[0] - breaks[1] ) / 100. ) )
319  {
320  breaks.removeAt( i );
321  --i;
322  }
323  }
324  // remove symmetry point
325  if ( astride ) // && breaks.indexOf( symmetryPoint ) != -1) // if symmetryPoint is found
326  {
327  breaks.removeAt( breaks.indexOf( symmetryPoint ) );
328  }
329 }
330 
332 {
333  return labelForRange( range.lowerValue(), range.upperValue(), position );
334 }
335 
336 QString QgsClassificationMethod::labelForRange( const double lowerValue, const double upperValue, ClassPosition position ) const
337 {
338  Q_UNUSED( position )
339 
340  const QString lowerLabel = valueToLabel( lowerValue );
341  const QString upperLabel = valueToLabel( upperValue );
342 
343  return labelFormat().arg( lowerLabel ).arg( upperLabel );
344 }
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:35
QgsClassificationMethod::UpperBound
@ UpperBound
The class is at the upper bound.
Definition: qgsclassificationmethod.h:124
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:53
QgsClassificationMethod::writeXml
virtual void writeXml(QDomElement &element, const QgsReadWriteContext &context) const
Writes extra information about the method.
Definition: qgsclassificationmethod.h:158
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:37
QgsClassificationMethod::labelForRange
virtual QString labelForRange(double lowerValue, double upperValue, ClassPosition position=Inner) const
Returns the label for a range.
Definition: qgsclassificationmethod.cpp:336
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:331
QgsRendererRange::lowerValue
double lowerValue() const
Definition: qgsrendererrange.cpp:68
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:166
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:188
QgsVectorLayer::fields
QgsFields fields() const FINAL
Returns the list of fields of this layer.
Definition: qgsvectorlayer.cpp:3283
QgsClassificationMethod::LowerBound
@ LowerBound
The class is at the lower bound.
Definition: qgsclassificationmethod.h:122
QgsClassificationMethod::labelPrecision
int labelPrecision() const
Returns the precision for the formatting of the labels.
Definition: qgsclassificationmethod.h:211
precision
int precision
Definition: qgswfsgetfeature.cpp:49
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:209
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:300
QgsClassificationMethod::symmetricModeEnabled
bool symmetricModeEnabled() const
Returns if the symmetric mode is enabled.
Definition: qgsclassificationmethod.h:183
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:299
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:215
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:123
QgsVectorLayer::maximumValue
QVariant maximumValue(int index) const FINAL
Returns the maximum value for an attribute column or an invalid variant in case of error.
Definition: qgsvectorlayer.cpp:4181
QgsRendererRange::upperValue
double upperValue() const
Definition: qgsrendererrange.cpp:73
QgsApplication::classificationMethodRegistry
static QgsClassificationMethodRegistry * classificationMethodRegistry()
Returns the application's classification methods registry, used in graduated renderer.
Definition: qgsapplication.cpp:2233
qgsxmlutils.h
QgsVectorLayer::minimumValue
QVariant minimumValue(int index) const FINAL
Returns the minimum value for an attribute column or an invalid variant in case of error.
Definition: qgsvectorlayer.cpp:4176
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:160
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:207
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:298
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:387
QgsClassificationMethod::ClassPosition
ClassPosition
Defines the class position.
Definition: qgsclassificationmethod.h:121
QgsMessageLog::logMessage
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
Definition: qgsmessagelog.cpp:27
QgsClassificationMethod::setLabelTrimTrailingZeroes
void setLabelTrimTrailingZeroes(bool trimTrailingZeroes)
Defines if the trailing 0 are trimmed in the label.
Definition: qgsclassificationmethod.h:217
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:297
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:127