QGIS API Documentation 3.99.0-Master (2fe06baccd8)
Loading...
Searching...
No Matches
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
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 <limits>
19
20#include "qgsapplication.h"
21
22#include <QRandomGenerator>
23
29
31{
32 return QObject::tr( "Natural Breaks (Jenks)" );
33}
34
36{
37 return QStringLiteral( "Jenks" );
38}
39
40std::unique_ptr<QgsClassificationMethod> QgsClassificationJenks::clone() const
41{
42 auto c = std::make_unique< QgsClassificationJenks >();
43 copyBase( c.get() );
44 return c;
45}
46
48{
49 return QgsApplication::getThemeIcon( "classification_methods/mClassificationNaturalBreak.svg" );
50}
51
52
53QList<double> QgsClassificationJenks::calculateBreaks( double &minimum, double &maximum,
54 const QList<double> &values, int nclasses, QString &error )
55{
56 Q_UNUSED( error )
57 // Jenks Optimal (Natural Breaks) algorithm
58 // Based on the Jenks algorithm from the 'classInt' package available for
59 // the R statistical prgramming language, and from Python code from here:
60 // http://danieljlewis.org/2010/06/07/jenks-natural-breaks-algorithm-in-python/
61 // and is based on a JAVA and Fortran code available here:
62 // https://stat.ethz.ch/pipermail/r-sig-geo/2006-March/000811.html
63
64 // Returns class breaks such that classes are internally homogeneous while
65 // assuring heterogeneity among classes.
66
67 if ( values.isEmpty() )
68 return QList<double>();
69
70 if ( nclasses <= 1 )
71 {
72 return QList<double>() << maximum;
73 }
74
75 if ( nclasses >= values.size() )
76 {
77 return values;
78 }
79
80 QVector<double> sample;
81 QVector<double> sorted;
82
83 // if we have lots of values, we need to take a random sample
84 if ( values.size() > mMaximumSize )
85 {
86 // for now, sample at least maximumSize values or a 10% sample, whichever
87 // is larger. This will produce a more representative sample for very large
88 // layers, but could end up being computationally intensive...
89
90 sample.resize( std::max( mMaximumSize, static_cast<int>( values.size() ) / 10 ) );
91
92 QgsDebugMsgLevel( QStringLiteral( "natural breaks (jenks) sample size: %1" ).arg( sample.size() ), 2 );
93 QgsDebugMsgLevel( QStringLiteral( "values:%1" ).arg( values.size() ), 2 );
94
95 sample[ 0 ] = minimum;
96 sample[ 1 ] = maximum;
97
98 sorted = values.toVector();
99 std::sort( sorted.begin(), sorted.end() );
100
101 int j = -1;
102
103 // loop through all values in initial array
104 // skip the first value as it is a minimum one
105 // skip the last value as that one is a maximum one
106 // and those are already in the sample as items 0 and 1
107 for ( int i = 1; i < sorted.size() - 2; i++ )
108 {
109 if ( ( i * ( mMaximumSize - 2 ) / ( sorted.size() - 2 ) ) > j )
110 {
111 j++;
112 sample[ j + 2 ] = sorted[ i ];
113 }
114 }
115 }
116 else
117 {
118 sample = values.toVector();
119 }
120
121 const int n = sample.size();
122
123 // sort the sample values
124 std::sort( sample.begin(), sample.end() );
125
126 QVector< QVector<int> > matrixOne( n + 1 );
127 QVector< QVector<double> > matrixTwo( n + 1 );
128
129 for ( int i = 0; i <= n; i++ )
130 {
131 matrixOne[i].resize( nclasses + 1 );
132 matrixTwo[i].resize( nclasses + 1 );
133 }
134
135 for ( int i = 1; i <= nclasses; i++ )
136 {
137 matrixOne[0][i] = 1;
138 matrixOne[1][i] = 1;
139 matrixTwo[0][i] = 0.0;
140 for ( int j = 2; j <= n; j++ )
141 {
142 matrixTwo[j][i] = std::numeric_limits<double>::max();
143 }
144 }
145
146 for ( int l = 2; l <= n; l++ )
147 {
148 double s1 = 0.0;
149 double s2 = 0.0;
150 int w = 0;
151
152 double v = 0.0;
153
154 for ( int m = 1; m <= l; m++ )
155 {
156 const int i3 = l - m + 1;
157
158 const double val = sample[ i3 - 1 ];
159
160 s2 += val * val;
161 s1 += val;
162 w++;
163
164 v = s2 - ( s1 * s1 ) / static_cast< double >( w );
165 const int i4 = i3 - 1;
166 if ( i4 != 0 )
167 {
168 for ( int j = 2; j <= nclasses; j++ )
169 {
170 if ( matrixTwo[l][j] >= v + matrixTwo[i4][j - 1] )
171 {
172 matrixOne[l][j] = i4;
173 matrixTwo[l][j] = v + matrixTwo[i4][j - 1];
174 }
175 }
176 }
177 }
178 matrixOne[l][1] = 1;
179 matrixTwo[l][1] = v;
180 }
181
182 QVector<double> breaks( nclasses );
183 breaks[nclasses - 1] = sample[n - 1];
184
185 for ( int j = nclasses, k = n; j >= 2; j-- )
186 {
187 const int id = matrixOne[k][j] - 1;
188 breaks[j - 2] = sample[id];
189 k = matrixOne[k][j] - 1;
190 }
191
192 return breaks.toList();
193}
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
QIcon icon() const override
The icon of the method.
QString name() const override
The readable and translate name of the method.
std::unique_ptr< QgsClassificationMethod > clone() const override
Returns a clone of the method.
QString id() const override
The id of the method as saved in the project, must be unique in registry.
QgsClassificationMethod(MethodProperties properties=NoFlag, int codeComplexity=1)
Creates a classification method.
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:61