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