Skip to content

Commit

Permalink
[FEATURE] Allow classifying paletted renderer using unique values
Browse files Browse the repository at this point in the history
from a raster layer

Adds an easy way to style discrete rasters such as landuse classes
using the Paletted renderer. Just select the Paletted renderer,
pick a band, then hit the "Add Unique Values" button. The unique
pixel values will be fetched from the layer and a color assigned
to each using the currently selected color ramp.

Fix #14845

Sponsored by
- Stéphane Henriod
- Satelligence (http://satelligence.com/)
- Bird's Eye View (https://www.birdseyeviewgis.com/)
- + other anonymous backers
  • Loading branch information
nyalldawson committed Apr 3, 2017
1 parent c8b728a commit d03844d
Show file tree
Hide file tree
Showing 8 changed files with 144 additions and 1 deletion.
1 change: 1 addition & 0 deletions python/core/raster/qgspalettedrasterrenderer.sip
Expand Up @@ -40,6 +40,7 @@ class QgsPalettedRasterRenderer : QgsRasterRenderer
static QgsPalettedRasterRenderer::ClassData classDataFromString( const QString &string );
static QgsPalettedRasterRenderer::ClassData classDataFromFile( const QString &path );
static QString classDataToString( const QgsPalettedRasterRenderer::ClassData &classes );
static QgsPalettedRasterRenderer::ClassData classDataFromRaster( QgsRasterInterface *raster, int bandNumber, QgsColorRamp *ramp = 0 );

private:

Expand Down
44 changes: 44 additions & 0 deletions src/core/raster/qgspalettedrasterrenderer.cpp
Expand Up @@ -401,6 +401,50 @@ QString QgsPalettedRasterRenderer::classDataToString( const QgsPalettedRasterRen
return out.join( '\n' );
}

QgsPalettedRasterRenderer::ClassData QgsPalettedRasterRenderer::classDataFromRaster( QgsRasterInterface *raster, int bandNumber, QgsColorRamp *ramp )
{
if ( !raster )
return ClassData();

// get min and max value from raster
QgsRasterBandStats stats = raster->bandStatistics( bandNumber, QgsRasterBandStats::Min | QgsRasterBandStats::Max );
double min = stats.minimumValue;
double max = stats.maximumValue;
// need count of every individual value
int bins = ceil( max - min ) + 1;

QgsRasterHistogram histogram = raster->histogram( bandNumber, bins, min, max );
double interval = ( histogram.maximum - histogram.minimum + 1 ) / histogram.binCount;

ClassData data;

double currentValue = histogram.minimum;
double presentValues = 0;
for ( int idx = 0; idx < histogram.binCount; ++idx )
{
int count = histogram.histogramVector.at( idx );
if ( count > 0 )
{
data << Class( currentValue, QColor(), QString::number( currentValue ) );
presentValues++;
}
currentValue += interval;
}

// assign colors from ramp
if ( ramp )
{
int i = 0;
QgsPalettedRasterRenderer::ClassData::iterator cIt = data.begin();
for ( ; cIt != data.end(); ++cIt )
{
cIt->color = ramp->color( i / presentValues );
i++;
}
}
return data;
}

void QgsPalettedRasterRenderer::updateArrays()
{
// find maximum color index
Expand Down
7 changes: 7 additions & 0 deletions src/core/raster/qgspalettedrasterrenderer.h
Expand Up @@ -135,6 +135,13 @@ class CORE_EXPORT QgsPalettedRasterRenderer: public QgsRasterRenderer
*/
static QString classDataToString( const QgsPalettedRasterRenderer::ClassData &classes );

/**
* Generates class data from a \a raster, for the specified \a bandNumber. An optional
* color \a ramp can be specified to automatically assign colors from the ramp.
* @note added in QGIS 3.0
*/
static QgsPalettedRasterRenderer::ClassData classDataFromRaster( QgsRasterInterface *raster, int bandNumber, QgsColorRamp *ramp = nullptr );

private:

int mBand;
Expand Down
18 changes: 18 additions & 0 deletions src/gui/raster/qgspalettedrendererwidget.cpp
Expand Up @@ -93,6 +93,7 @@ QgsPalettedRendererWidget::QgsPalettedRendererWidget( QgsRasterLayer *layer, con
connect( mAddEntryButton, &QPushButton::clicked, this, &QgsPalettedRendererWidget::addEntry );
connect( mLoadFromFileButton, &QPushButton::clicked, this, &QgsPalettedRendererWidget::loadColorTable );
connect( mExportToFileButton, &QPushButton::clicked, this, &QgsPalettedRendererWidget::saveColorTable );
connect( mClassifyButton, &QPushButton::clicked, this, &QgsPalettedRendererWidget::classify );
}

QgsRasterRenderer *QgsPalettedRendererWidget::renderer()
Expand Down Expand Up @@ -367,6 +368,23 @@ void QgsPalettedRendererWidget::saveColorTable()
}
}

void QgsPalettedRendererWidget::classify()
{
if ( mRasterLayer )
{
QgsRasterDataProvider *provider = mRasterLayer->dataProvider();
if ( !provider )
{
return;
}

std::unique_ptr< QgsColorRamp > ramp( btnColorRamp->colorRamp() );
QgsPalettedRasterRenderer::ClassData data = QgsPalettedRasterRenderer::classDataFromRaster( provider, mBandComboBox->currentData().toInt(), ramp.get() );
mModel->setClassData( data );
emit widgetChanged();
}
}

//
// QgsPalettedRendererModel
//
Expand Down
1 change: 1 addition & 0 deletions src/gui/raster/qgspalettedrendererwidget.h
Expand Up @@ -109,6 +109,7 @@ class GUI_EXPORT QgsPalettedRendererWidget: public QgsRasterRendererWidget, priv
void applyColorRamp();
void loadColorTable();
void saveColorTable();
void classify();

};

Expand Down
7 changes: 7 additions & 0 deletions src/ui/qgspalettedrendererwidgetbase.ui
Expand Up @@ -60,6 +60,13 @@
</item>
<item row="3" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QPushButton" name="mClassifyButton">
<property name="text">
<string>Classify</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="mAddEntryButton">
<property name="toolTip">
Expand Down
67 changes: 66 additions & 1 deletion tests/src/python/test_qgsrasterlayer.py
Expand Up @@ -35,7 +35,8 @@
QgsPalettedRasterRenderer,
QgsSingleBandGrayRenderer,
QgsSingleBandPseudoColorRenderer,
QgsLimitedRandomColorRamp)
QgsLimitedRandomColorRamp,
QgsGradientColorRamp)
from utilities import unitTestDataPath
from qgis.testing import start_app, unittest

Expand Down Expand Up @@ -553,6 +554,70 @@ def testPalettedClassDataToString(self):
self.assertEqual(QgsPalettedRasterRenderer.classDataToString(classes),
'3 255 0 0 255 class 1\n4 0 255 0 255 class 2')

def testPalettedClassDataFromLayer(self):
# no layer
classes = QgsPalettedRasterRenderer.classDataFromRaster(None, 1)
self.assertFalse(classes)

# 10 class layer
path = os.path.join(unitTestDataPath('raster'),
'with_color_table.tif')
info = QFileInfo(path)
base_name = info.baseName()
layer10 = QgsRasterLayer(path, base_name)
classes = QgsPalettedRasterRenderer.classDataFromRaster(layer10.dataProvider(), 1)
self.assertEqual(len(classes), 10)
self.assertEqual(classes[0].value, 1)
self.assertEqual(classes[0].label, '1')
self.assertEqual(classes[1].value, 2)
self.assertEqual(classes[1].label, '2')
self.assertEqual(classes[2].value, 3)
self.assertEqual(classes[2].label, '3')
self.assertEqual(classes[3].value, 4)
self.assertEqual(classes[3].label, '4')
self.assertEqual(classes[4].value, 5)
self.assertEqual(classes[4].label, '5')
self.assertEqual(classes[5].value, 6)
self.assertEqual(classes[5].label, '6')
self.assertEqual(classes[6].value, 7)
self.assertEqual(classes[6].label, '7')
self.assertEqual(classes[7].value, 8)
self.assertEqual(classes[7].label, '8')
self.assertEqual(classes[8].value, 9)
self.assertEqual(classes[8].label, '9')
self.assertEqual(classes[9].value, 10)
self.assertEqual(classes[9].label, '10')

# bad band
with self.assertRaises(Exception):
classes = QgsPalettedRasterRenderer.classDataFromRaster(layer10.dataProvider(), 10101010)

# with ramp
r = QgsGradientColorRamp(QColor(200, 0, 0, 100), QColor(0, 200, 0, 200))
classes = QgsPalettedRasterRenderer.classDataFromRaster(layer10.dataProvider(), 1, r)
self.assertEqual(len(classes), 10)
self.assertEqual(classes[0].color.name(), '#c80000')
self.assertEqual(classes[1].color.name(), '#b41400')
self.assertEqual(classes[2].color.name(), '#a02800')
self.assertEqual(classes[3].color.name(), '#8c3c00')
self.assertEqual(classes[4].color.name(), '#785000')
self.assertEqual(classes[5].color.name(), '#646400')
self.assertEqual(classes[6].color.name(), '#507800')
self.assertEqual(classes[7].color.name(), '#3c8c00')
self.assertEqual(classes[8].color.name(), '#28a000')
self.assertEqual(classes[9].color.name(), '#14b400')

# 30 class layer
path = os.path.join(unitTestDataPath('raster'),
'unique_1.tif')
info = QFileInfo(path)
base_name = info.baseName()
layer10 = QgsRasterLayer(path, base_name)
classes = QgsPalettedRasterRenderer.classDataFromRaster(layer10.dataProvider(), 1)
self.assertEqual(len(classes), 30)
expected = [11, 21, 22, 24, 31, 82, 2002, 2004, 2014, 2019, 2027, 2029, 2030, 2080, 2081, 2082, 2088, 2092, 2097, 2098, 2099, 2105, 2108, 2110, 2114, 2118, 2126, 2152, 2184, 2220]
self.assertEqual([c.value for c in classes], expected)


if __name__ == '__main__':
unittest.main()
Binary file added tests/testdata/raster/unique_1.tif
Binary file not shown.

0 comments on commit d03844d

Please sign in to comment.