QGIS API Documentation  3.18.1-Zürich (202f1bf7e5)
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
qgsclassificationjenks.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsclassificationjenks.h
3  ---------------------
4  begin : September 2019
5  copyright : (C) 2019 by Denis Rouzaud
6  email : denis@opengis.ch
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 <limits>
17 #include "qgsclassificationjenks.h"
18 #include "qgsapplication.h"
19 
20 #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
21 #include <QRandomGenerator>
22 #endif
23 
26 {
27 
28 }
29 
31 {
32  return QObject::tr( "Natural Breaks (Jenks)" );
33 }
34 
36 {
37  return QStringLiteral( "Jenks" );
38 }
39 
41 {
43  copyBase( c );
44  return c;
45 }
46 
48 {
49  return QgsApplication::getThemeIcon( "classification_methods/mClassificationNaturalBreak.svg" );
50 }
51 
52 
53 QList<double> QgsClassificationJenks::calculateBreaks( double &minimum, double &maximum,
54  const QList<double> &values, int nclasses )
55 {
56  // Jenks Optimal (Natural Breaks) algorithm
57  // Based on the Jenks algorithm from the 'classInt' package available for
58  // the R statistical prgramming language, and from Python code from here:
59  // http://danieljlewis.org/2010/06/07/jenks-natural-breaks-algorithm-in-python/
60  // and is based on a JAVA and Fortran code available here:
61  // https://stat.ethz.ch/pipermail/r-sig-geo/2006-March/000811.html
62 
63  // Returns class breaks such that classes are internally homogeneous while
64  // assuring heterogeneity among classes.
65 
66  if ( values.isEmpty() )
67  return QList<double>();
68 
69  if ( nclasses <= 1 )
70  {
71  return QList<double>() << maximum;
72  }
73 
74  if ( nclasses >= values.size() )
75  {
76  return values;
77  }
78 
79  QVector<double> sample;
80 
81  // if we have lots of values, we need to take a random sample
82  if ( values.size() > mMaximumSize )
83  {
84  // for now, sample at least maximumSize values or a 10% sample, whichever
85  // is larger. This will produce a more representative sample for very large
86  // layers, but could end up being computationally intensive...
87 
88  sample.resize( std::max( mMaximumSize, values.size() / 10 ) );
89 
90  QgsDebugMsgLevel( QStringLiteral( "natural breaks (jenks) sample size: %1" ).arg( sample.size() ), 2 );
91  QgsDebugMsgLevel( QStringLiteral( "values:%1" ).arg( values.size() ), 2 );
92 
93  sample[ 0 ] = minimum;
94  sample[ 1 ] = maximum;
95 
96  for ( int i = 2; i < sample.size(); i++ )
97  {
98  // pick a random integer from 0 to n
99 
100 #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
101  double r = QRandomGenerator::global()->generate();
102 #else
103  double r = qrand();
104 #endif
105  int j = std::floor( r / RAND_MAX * ( values.size() - 1 ) );
106  sample[ i ] = values[ j ];
107  }
108  }
109  else
110  {
111  sample = values.toVector();
112  }
113 
114  int n = sample.size();
115 
116  // sort the sample values
117  std::sort( sample.begin(), sample.end() );
118 
119  QVector< QVector<int> > matrixOne( n + 1 );
120  QVector< QVector<double> > matrixTwo( n + 1 );
121 
122  for ( int i = 0; i <= n; i++ )
123  {
124  matrixOne[i].resize( nclasses + 1 );
125  matrixTwo[i].resize( nclasses + 1 );
126  }
127 
128  for ( int i = 1; i <= nclasses; i++ )
129  {
130  matrixOne[0][i] = 1;
131  matrixOne[1][i] = 1;
132  matrixTwo[0][i] = 0.0;
133  for ( int j = 2; j <= n; j++ )
134  {
135  matrixTwo[j][i] = std::numeric_limits<double>::max();
136  }
137  }
138 
139  for ( int l = 2; l <= n; l++ )
140  {
141  double s1 = 0.0;
142  double s2 = 0.0;
143  int w = 0;
144 
145  double v = 0.0;
146 
147  for ( int m = 1; m <= l; m++ )
148  {
149  int i3 = l - m + 1;
150 
151  double val = sample[ i3 - 1 ];
152 
153  s2 += val * val;
154  s1 += val;
155  w++;
156 
157  v = s2 - ( s1 * s1 ) / static_cast< double >( w );
158  int i4 = i3 - 1;
159  if ( i4 != 0 )
160  {
161  for ( int j = 2; j <= nclasses; j++ )
162  {
163  if ( matrixTwo[l][j] >= v + matrixTwo[i4][j - 1] )
164  {
165  matrixOne[l][j] = i4;
166  matrixTwo[l][j] = v + matrixTwo[i4][j - 1];
167  }
168  }
169  }
170  }
171  matrixOne[l][1] = 1;
172  matrixTwo[l][1] = v;
173  }
174 
175  QVector<double> breaks( nclasses );
176  breaks[nclasses - 1] = sample[n - 1];
177 
178  for ( int j = nclasses, k = n; j >= 2; j-- )
179  {
180  int id = matrixOne[k][j] - 1;
181  breaks[j - 2] = sample[id];
182  k = matrixOne[k][j] - 1;
183  }
184 
185  return breaks.toList();
186 }
static QIcon getThemeIcon(const QString &name)
Helper to get a theme icon.
QgsClassificationJenks is an implementation of QgsClassificationMethod for natural breaks based on Je...
QgsClassificationMethod * clone() const override
Returns a clone of the method.
QIcon icon() const override
The icon of the method.
QString name() const override
The readable and translate name of the method.
QString id() const override
The id of the method as saved in the project, must be unique in registry.
QgsClassificationMethod is an abstract class for implementations of classification methods.
void copyBase(QgsClassificationMethod *c) const
Copy the parameters (shall be used in clone implementation)
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 QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39