[QGIS Commit] r14308 - in trunk/qgis/src: core/symbology-ng gui/symbology-ng ui

svn_qgis at osgeo.org svn_qgis at osgeo.org
Tue Sep 28 20:26:34 EDT 2010


Author: cfarmer
Date: 2010-09-29 00:26:34 +0000 (Wed, 29 Sep 2010)
New Revision: 14308

Modified:
   trunk/qgis/src/core/symbology-ng/qgsgraduatedsymbolrendererv2.cpp
   trunk/qgis/src/core/symbology-ng/qgsgraduatedsymbolrendererv2.h
   trunk/qgis/src/gui/symbology-ng/qgsgraduatedsymbolrendererv2widget.cpp
   trunk/qgis/src/ui/qgsgraduatedsymbolrendererv2widget.ui
Log:
Adds three new classification modes to graduated symbol renderer (version 2), including Natural Breaks (Jenks), Standard Deviations, and Pretty Breaks (based on pretty from the R statistical environment)

Modified: trunk/qgis/src/core/symbology-ng/qgsgraduatedsymbolrendererv2.cpp
===================================================================
--- trunk/qgis/src/core/symbology-ng/qgsgraduatedsymbolrendererv2.cpp	2010-09-28 20:41:38 UTC (rev 14307)
+++ trunk/qgis/src/core/symbology-ng/qgsgraduatedsymbolrendererv2.cpp	2010-09-29 00:26:34 UTC (rev 14308)
@@ -1,4 +1,16 @@
+/***************************************************************************
 
+qgsgraduatedsymbolrendererv2.cpp - Graduated Symbol Renderer Version 2
+
+/***************************************************************************
+*                                                                         *
+*   This program is free software; you can redistribute it and/or modify  *
+*   it under the terms of the GNU General Public License as published by  *
+*   the Free Software Foundation; either version 2 of the License, or     *
+*   (at your option) any later version.                                   *
+*                                                                         *
+***************************************************************************/
+
 #include "qgsgraduatedsymbolrendererv2.h"
 
 #include "qgssymbolv2.h"
@@ -12,6 +24,8 @@
 #include <QDomDocument>
 #include <QDomElement>
 #include <QSettings> // for legend
+#include <limits> // for jenks classification
+#include "math.h" // for pretty classification
 
 QgsRendererRangeV2::QgsRendererRangeV2( double lowerValue, double upperValue, QgsSymbolV2* symbol, QString label )
     : mLowerValue( lowerValue ), mUpperValue( upperValue ), mSymbol( symbol ), mLabel( label )
@@ -273,6 +287,12 @@
 
 static QList<double> _calcEqualIntervalBreaks( double minimum, double maximum, int classes )
 {
+
+  // Equal interval algorithm
+  //
+  // Returns breaks based on dividing the range ('minimum' to 'maximum')
+  // into 'classes' parts.
+
   double step = ( maximum - minimum ) / classes;
 
   QList<double> breaks;
@@ -287,11 +307,7 @@
 
 static QList<double> _calcQuantileBreaks( QList<double> values, int classes )
 {
-  // sort the values first
-  qSort( values );
 
-  QList<double> breaks;
-
   // q-th quantile of a data set:
   // value where q fraction of data is below and (1-q) fraction is above this value
   // Xq = (1 - r) * X_NI1 + r * X_NI2
@@ -300,6 +316,11 @@
   //   r = q * (n+1) - (int) (q * (n+1))
   // (indices of X: 1...n)
 
+  // sort the values first
+  qSort( values );
+
+  QList<double> breaks;
+
   int n = values.count();
   double q, a, aa, r, Xq;
   for ( int i = 0; i < ( classes - 1 ); i++ )
@@ -319,6 +340,328 @@
   return breaks;
 }
 
+static QList<double> _calcPrettyBreaks( double minimum, double maximum, int classes )
+{
+
+  // C++ implementation of R's pretty algorithm
+  // Based on code for determining optimal tick placement for statistical graphics
+  // from the R statistical programming language.
+  // Code ported from R implementation from 'labeling' R package
+  //
+  // Computes a sequence of about 'classes' equally spaced 'round' values
+  // which cover the range of of values from 'minimum' to 'maximum'.
+  // The values are chosen so that they are 1, 2 or 5 times a power of 10.
+
+  QList<double> breaks;
+  if ( classes < 1 )
+  {
+      breaks.append( maximum );
+      return breaks;
+  }
+
+  int minimumCount = (int) classes / 3;
+  double shrink = 0.75;
+  double highBias = 1.5;
+  double adjustBias = 0.5 + 1.5 * highBias;
+  int divisions = classes;
+  double h = highBias;
+  double cell;
+  int U;
+  bool small = false;
+  double dx = maximum - minimum;
+
+  if ( dx == 0 and maximum == 0 )
+  {
+    cell = 1.0;
+    small = true;
+    U = 1;
+  }
+  else
+  {
+    cell = qMax( qAbs( minimum ), qAbs( maximum ) );
+    if ( adjustBias >= 1.5 * h + 0.5 )
+    {
+      U = 1 + (1.0/(1 + h));
+    }
+    else
+    {
+      U = 1 + (1.5 / (1 + adjustBias));
+    }
+    small = dx < ( cell * U * qMax( 1, divisions ) * 1e-07 * 3.0 );
+  }
+
+  if ( small )
+  {
+    if ( cell > 10 )
+    {
+      cell = 9 + cell / 10;
+      cell = cell * shrink;
+    }
+    if ( minimumCount > 1 )
+    {
+      cell = cell / minimumCount;
+    }
+  }
+  else
+  {
+    cell = dx;
+    if ( divisions > 1 )
+    {
+      cell = cell / divisions;
+    }
+  }
+  if ( cell < 20 * 1e-07 )
+  {
+    cell = 20 * 1e-07;
+  }
+
+  double base = pow( 10.0, floor( log10( cell ) ) );
+  double unit = base;
+  if ( ( 2 * base ) - cell < h * ( cell - unit ) )
+  {
+    unit = 2.0 * base;
+    if ( ( 5 * base ) - cell < adjustBias * ( cell - unit ) )
+    {
+      unit = 5.0 * base;
+      if ( ( 10.0 * base ) - cell < h * ( cell - unit ) )
+      {
+        unit = 10.0 * base;
+      }
+    }
+  }
+  // Maybe used to correct for the epsilon here??
+  int start = floor( minimum / unit + 1e-07 );
+  int end = ceil( maximum / unit - 1e-07 );
+
+  // Extend the range out beyond the data. Does this ever happen??
+  while ( start * unit > minimum + ( 1e-07 * unit ) )
+  {
+    start = start - 1;
+  }
+  while ( end * unit < maximum - ( 1e-07 * unit ) )
+  {
+    end = end + 1;
+  }
+  QgsDebugMsg( QString( "pretty classes: %1" ).arg( end ) );
+
+  // If we don't have quite enough labels, extend the range out
+  // to make more (these labels are beyond the data :( )
+  int k = floor( 0.5 + end - start );
+  if ( k < minimumCount )
+  {
+    k = minimumCount - k;
+    if ( start >= 0 )
+    {
+      end = end + k / 2;
+      start = start - k / 2 + k % 2;
+    }
+    else
+    {
+      start = start - k / 2;
+      end = end + k / 2 + k % 2;
+    }
+    divisions = minimumCount;
+  }
+  else
+  {
+    divisions = k;
+  }
+  double minimumBreak = start * unit;
+  double maximumBreak = end * unit;
+  int count = ceil( maximumBreak - minimumBreak ) / unit;
+
+  for ( int i = 1; i < count+1; i++ )
+  {
+    breaks.append( minimumBreak + i * unit );
+  }
+
+  if ( breaks.first() < minimum )
+  {
+    breaks[0] = minimum;
+  }
+  if ( breaks.last() > maximum )
+  {
+    breaks[breaks.count()-1] = maximum;
+  }
+  return breaks;
+} // _calcPrettyBreaks
+
+
+static QList<double> _calcStdDevBreaks( QList<double> values, int classes, QList<int> &labels )
+{
+
+  // C++ implementation of the standard deviation class interval algorithm
+  // as implemented in the 'classInt' package available for the R statistical
+  // prgramming language.
+
+  // Returns breaks based on '_calcPrettyBreaks' of the centred and scaled
+  // values of 'values', and may have a number of classes different from 'classes'.
+
+  double mean = 0.0;
+  double stdDev = 0.0;
+  int n = values.count();
+  double minimum = values[0];
+  double maximum = values[0];
+
+  for ( int i = 0; i < n; i++ )
+  {
+    mean += values[i];
+    minimum = qMin( values[i], minimum ); // could use precomputed max and min
+    maximum = qMax( values[i], maximum ); // but have to go through entire list anyway
+  }
+  mean = mean / (double) n;
+
+  double sd = 0.0;
+  for ( int i = 0; i < n; i++ )
+  {
+    sd = values[i] - mean;
+    stdDev += sd * sd;
+  }
+  stdDev = sqrt( stdDev / n );
+
+  QList<double> breaks = _calcPrettyBreaks( ( minimum - mean ) / stdDev, ( maximum - mean ) / stdDev, classes );
+  for ( int i = 0; i < breaks.count(); i++ )
+  {
+    labels.append( (int) breaks[i]);
+    breaks[i] = ( breaks[i] * stdDev ) + mean;
+  }
+
+  return breaks;
+} // _calcStdDevBreaks
+
+static QList<double> _calcJenksBreaks( QList<double> values, int classes,
+                                       double minimum, double maximum,
+                                       int maximumSize = 1000 )
+{
+
+  // Jenks Optimal (Natural Breaks) algorithm
+  // Based on the Jenks algorithm from the 'classInt' package available for
+  // the R statistical prgramming language, and from Python code from here:
+  // http://danieljlewis.org/2010/06/07/jenks-natural-breaks-algorithm-in-python/
+  // and is based on a JAVA and Fortran code available here:
+  // https://stat.ethz.ch/pipermail/r-sig-geo/2006-March/000811.html
+
+  // Returns class breaks such that classes are internally homogeneous while
+  // assuring heterogeneity among classes.
+
+  QList<double> breaks;
+  if ( classes < 1 )
+  {
+      breaks.append( maximum );
+      return breaks;
+  }
+
+  int n = values.count();
+  QList<double> sample;
+
+  // if we have lots of values, we need to take a random sample
+  if ( n > maximumSize )
+  {
+    // for now, sample at least maximumSize values or a 10% sample, whichever
+    // is larger. This will produce a more representative sample for very large
+    // layers, but could end up being computationally intensive...
+    n = qMax( maximumSize, (int) ( (double) n * 0.10 ) );
+    QgsDebugMsg( QString( "natural breaks (jenks) sample size: %1" ).arg( n ) );
+    sample.append( minimum );
+    sample.append( maximum );
+    for ( int i = 0; i < n-2; i++ )
+    {
+      // pick a random integer from 0 to n
+      int c = (int) ( (double) rand() / ( (double) RAND_MAX + 1 ) * n - 1 );
+      sample.append( values[i+c] );
+    }
+  }
+  else
+  {
+      sample = values;
+  }
+  // sort the values
+  qSort( sample );
+
+  QList< QList<double> > matrixOne;
+  QList< QList<double> > matrixTwo;
+
+  double v, s1, s2, w, i3, i4, val;
+
+  for ( int i = 0; i < n + 1; i++ )
+  {
+    QList<double > tempOne;
+    QList<double > tempTwo;
+    for (int j = 0; j < classes + 1; j++)
+    {
+      tempOne.append(0.0);
+      tempTwo.append(0.0);
+    }
+    matrixOne.append(tempOne);
+    matrixTwo.append(tempTwo);
+  }
+
+  for ( int i = 1; i < classes + 1; i++ )
+  {
+    matrixOne[1][i] = 1.0;
+    matrixTwo[1][i] = 0.0;
+    for ( int j = 2; j < n + 1; j++ )
+    {
+      matrixTwo[j][i] = std::numeric_limits<qreal>::max();
+    }
+  }
+
+  v = 0.0;
+  for ( int l = 2; l < n + 1; l++ )
+  {
+    s1 = 0.0;
+    s2 = 0.0;
+    w = 0.0;
+    for ( int m = 1; m < l + 1; m++ )
+    {
+      i3 = l - m + 1;
+
+      val = sample[ i3 - 1 ];
+
+      s2 += val * val;
+      s1 += val;
+
+      w += 1.0;
+      v = s2 - (s1 * s1) / w;
+      i4 = i3 - 1;
+
+      if ( i4 != 0.0 )
+      {
+        for ( int j = 2; j < classes + 1; j++ )
+        {
+          if ( matrixTwo[l][j] >= v + matrixTwo[i4][j - 1] )
+          {
+            matrixOne[l][j] = i3;
+            matrixTwo[l][j] = v + matrixTwo[i4][j - 1];
+          }
+        }
+      }
+    }
+    matrixOne[l][1] = 1.0;
+    matrixTwo[l][1] = v;
+  }
+
+  for ( int i = 0; i < classes; i++ )
+  {
+    breaks.append(0.0);
+  }
+
+  breaks[classes - 1] = sample[sample.length() - 1];
+//  breaks[0] = values[0];
+
+  int k = n;
+  int count = classes;
+  while ( count >= 2 )
+  {
+    int id = matrixOne[k][count] - 2;
+    breaks[count - 2] = sample[id];
+    k = matrixOne[k][count] - 1;
+    count -= 1;
+  }
+
+  return breaks;
+} //_calcJenksBreaks
+
 #include "qgsvectordataprovider.h"
 #include "qgsvectorcolorrampv2.h"
 
@@ -339,12 +682,17 @@
   QgsDebugMsg( QString( "min %1 // max %2" ).arg( minimum ).arg( maximum ) );
 
   QList<double> breaks;
+  QList<int> labels;
   if ( mode == EqualInterval )
   {
     breaks = _calcEqualIntervalBreaks( minimum, maximum, classes );
   }
-  else if ( mode == Quantile )
+  else if ( mode == Pretty )
   {
+    breaks = _calcPrettyBreaks( minimum, maximum, classes );
+  }
+  else if ( mode == Quantile || mode == Jenks || mode == StdDev )
+  {
     // get values from layer
     QList<double> values;
     QgsFeature f;
@@ -354,7 +702,18 @@
     while ( provider->nextFeature( f ) )
       values.append( f.attributeMap()[attrNum].toDouble() );
     // calculate the breaks
-    breaks = _calcQuantileBreaks( values, classes );
+    if ( mode == Quantile )
+    {
+        breaks = _calcQuantileBreaks( values, classes );
+    }
+    else if ( mode == Jenks )
+    {
+        breaks = _calcJenksBreaks( values, classes, minimum, maximum );
+    }
+    else if ( mode == StdDev )
+    {
+        breaks = _calcStdDevBreaks( values, classes, labels );
+    }
   }
   else
   {
@@ -371,10 +730,28 @@
   {
     lower = upper; // upper border from last interval
     upper = *it;
-    label = QString::number( lower, 'f', 4 ) + " - " + QString::number( upper, 'f', 4 );
+    if ( mode == StdDev )
+    {
+      if ( i == 0 )
+      {
+        label = "< " + QString::number( labels[i], 'i', 0 ) + " Std Dev";
+      }
+      else if ( i == labels.count() - 1 )
+      {
+        label = ">= " + QString::number( labels[i-1], 'i', 0 ) + " Std Dev";
+      }
+      else
+      {
+        label = QString::number( labels[i-1], 'i', 0 ) + " Std Dev" + " - " + QString::number( labels[i], 'i', 0 ) + " Std Dev";
+      }
+    }
+    else
+    {
+      label = QString::number( lower, 'f', 4 ) + " - " + QString::number( upper, 'f', 4 );
+    }
 
     QgsSymbolV2* newSymbol = symbol->clone();
-    newSymbol->setColor( ramp->color(( double ) i / ( classes - 1 ) ) ); // color from (0 / cl-1) to (cl-1 / cl-1)
+    newSymbol->setColor( ramp->color(( double ) i / ( breaks.count() - 1 ) ) ); // color from (0 / cl-1) to (cl-1 / cl-1)
 
     ranges.append( QgsRendererRangeV2( lower, upper, newSymbol, label ) );
   }
@@ -454,6 +831,12 @@
       r->setMode( EqualInterval );
     else if ( modeString == "quantile" )
       r->setMode( Quantile );
+    else if ( modeString == "jenks" )
+      r->setMode( Jenks );
+    else if ( modeString == "stddev" )
+      r->setMode( StdDev );
+    else if ( modeString == "pretty" )
+      r->setMode( Pretty );
   }
 
   QDomElement rotationElem = element.firstChildElement( "rotation" );
@@ -523,6 +906,12 @@
     modeString = "equal";
   else if ( mMode == Quantile )
     modeString = "quantile";
+  else if ( mMode == Jenks )
+    modeString = "jenks";
+  else if ( mMode == StdDev )
+    modeString = "stddev";
+  else if ( mMode == Pretty )
+    modeString = "pretty";
   if ( !modeString.isEmpty() )
   {
     QDomElement modeElem = doc.createElement( "mode" );

Modified: trunk/qgis/src/core/symbology-ng/qgsgraduatedsymbolrendererv2.h
===================================================================
--- trunk/qgis/src/core/symbology-ng/qgsgraduatedsymbolrendererv2.h	2010-09-28 20:41:38 UTC (rev 14307)
+++ trunk/qgis/src/core/symbology-ng/qgsgraduatedsymbolrendererv2.h	2010-09-29 00:26:34 UTC (rev 14308)
@@ -74,6 +74,9 @@
     {
       EqualInterval,
       Quantile,
+      Jenks,
+      StdDev,
+      Pretty,
       Custom
     };
 

Modified: trunk/qgis/src/gui/symbology-ng/qgsgraduatedsymbolrendererv2widget.cpp
===================================================================
--- trunk/qgis/src/gui/symbology-ng/qgsgraduatedsymbolrendererv2widget.cpp	2010-09-28 20:41:38 UTC (rev 14307)
+++ trunk/qgis/src/gui/symbology-ng/qgsgraduatedsymbolrendererv2widget.cpp	2010-09-29 00:26:34 UTC (rev 14308)
@@ -192,7 +192,13 @@
   QgsGraduatedSymbolRendererV2::Mode mode;
   if ( cboGraduatedMode->currentIndex() == 0 )
     mode = QgsGraduatedSymbolRendererV2::EqualInterval;
-  else
+  else if ( cboGraduatedMode->currentIndex() == 2 )
+    mode = QgsGraduatedSymbolRendererV2::Jenks;
+  else if ( cboGraduatedMode->currentIndex() == 3 )
+    mode = QgsGraduatedSymbolRendererV2::StdDev;
+  else if ( cboGraduatedMode->currentIndex() == 4 )
+    mode = QgsGraduatedSymbolRendererV2::Pretty;
+  else // default should be quantile for now
     mode = QgsGraduatedSymbolRendererV2::Quantile;
 
   // create and set new renderer

Modified: trunk/qgis/src/ui/qgsgraduatedsymbolrendererv2widget.ui
===================================================================
--- trunk/qgis/src/ui/qgsgraduatedsymbolrendererv2widget.ui	2010-09-28 20:41:38 UTC (rev 14307)
+++ trunk/qgis/src/ui/qgsgraduatedsymbolrendererv2widget.ui	2010-09-29 00:26:34 UTC (rev 14308)
@@ -113,6 +113,21 @@
          <string>Quantile</string>
         </property>
        </item>
+       <item>
+        <property name="text">
+         <string>Natural Breaks (Jenks)</string>
+        </property>
+       </item>
+       <item>
+        <property name="text">
+         <string>Standard Deviation</string>
+        </property>
+       </item>
+       <item>
+        <property name="text">
+         <string>Pretty Breaks</string>
+        </property>
+       </item>
       </widget>
      </item>
      <item row="1" column="2" rowspan="2">



More information about the QGIS-commit mailing list