Skip to content

Commit

Permalink
[FEATURE] Implement saving paletted raster renderer color tables
Browse files Browse the repository at this point in the history
Use the .clr/gdal file format, but add the labels on the ends of
the lines. Seems other importers like ArcMap just ignore these.
  • Loading branch information
nyalldawson committed Apr 3, 2017
1 parent d0566f7 commit 7eb63d9
Show file tree
Hide file tree
Showing 7 changed files with 106 additions and 7 deletions.
1 change: 1 addition & 0 deletions python/core/raster/qgspalettedrasterrenderer.sip
Expand Up @@ -39,6 +39,7 @@ class QgsPalettedRasterRenderer : QgsRasterRenderer
static QgsPalettedRasterRenderer::ClassData colorTableToClassData( const QList<QgsColorRampShader::ColorRampItem> &table );
static QgsPalettedRasterRenderer::ClassData classDataFromString( const QString &string );
static QgsPalettedRasterRenderer::ClassData classDataFromFile( const QString &path );
static QString classDataToString( const QgsPalettedRasterRenderer::ClassData &classes );

private:

Expand Down
34 changes: 30 additions & 4 deletions src/core/raster/qgspalettedrasterrenderer.cpp
Expand Up @@ -326,9 +326,11 @@ QgsPalettedRasterRenderer::ClassData QgsPalettedRasterRenderer::classDataFromStr
break;
}

case 4:
case 5:
default:
{
if ( lineParts.count() < 4 )
continue;

int value = lineParts.at( 0 ).toInt( &ok );
if ( !ok )
continue;
Expand All @@ -346,14 +348,20 @@ QgsPalettedRasterRenderer::ClassData QgsPalettedRasterRenderer::classDataFromStr
c = QColor( r, g, b );
}

if ( lineParts.count() == 5 )
if ( lineParts.count() >= 5 )
{
double alpha = lineParts.at( 4 ).toDouble( &ok );
if ( ok )
c.setAlpha( alpha );
}

classes << Class( value, c );
QString label;
if ( lineParts.count() > 5 )
{
label = lineParts.mid( 5 ).join( ' ' );

This comment has been minimized.

Copy link
@blazek

blazek May 22, 2017

Member

This line gives me compilation error: no member named 'join' in 'QList<QString>'
which seems to be right, because mid() returns QList, not QStringList which has join().
How can you compile that line?

This comment has been minimized.

Copy link
@nyalldawson

nyalldawson May 22, 2017

Author Collaborator

What Qt version & compiler are you using? It's odd... I can't see how this works now (and can't recall anything special with it), but it's compiling and working on Travis/windows/osx etc.

This comment has been minimized.

Copy link
@blazek

blazek May 22, 2017

Member

Qt 5.3.2 + clang 3.5 with -std=c++11. Old Qt was my suspicion but I checked Qt 5.8 doc and mid() also returns QList<QString>. Its obvious that it is compiling for everybody, but what is the trick?

I am installing Qt 5.8.

This comment has been minimized.

Copy link
@blazek

blazek May 24, 2017

Member

It compiles with Qt 5.8.

}

classes << Class( value, c, label );
break;
}
}
Expand All @@ -375,6 +383,24 @@ QgsPalettedRasterRenderer::ClassData QgsPalettedRasterRenderer::classDataFromFil
return classDataFromString( input );
}

QString QgsPalettedRasterRenderer::classDataToString( const QgsPalettedRasterRenderer::ClassData &classes )
{
QStringList out;
// must be sorted
QgsPalettedRasterRenderer::ClassData cd = classes;
std::sort( cd.begin(), cd.end(), []( const Class & a, const Class & b ) -> bool
{
return a.value < b.value;
} );

Q_FOREACH ( const Class &c, cd )
{
out << QString( "%1 %2 %3 %4 %5 %6" ).arg( c.value ).arg( c.color.red() )
.arg( c.color.green() ).arg( c.color.blue() ).arg( c.color.alpha() ).arg( c.label );
}
return out.join( '\n' );
}

void QgsPalettedRasterRenderer::updateArrays()
{
// find maximum color index
Expand Down
8 changes: 8 additions & 0 deletions src/core/raster/qgspalettedrasterrenderer.h
Expand Up @@ -117,6 +117,7 @@ class CORE_EXPORT QgsPalettedRasterRenderer: public QgsRasterRenderer
* Converts a \a string containing a color table or class data to to paletted renderer class data.
* @note added in QGIS 3.0
* @see classDataFromFile()
* @see classDataToString()
*/
static QgsPalettedRasterRenderer::ClassData classDataFromString( const QString &string );

Expand All @@ -127,6 +128,13 @@ class CORE_EXPORT QgsPalettedRasterRenderer: public QgsRasterRenderer
*/
static QgsPalettedRasterRenderer::ClassData classDataFromFile( const QString &path );

/**
* Converts classes to a string representation, using the .clr/gdal color table file format.
* @note added in QGIS 3.0
* @see classDataFromString()
*/
static QString classDataToString( const QgsPalettedRasterRenderer::ClassData &classes );

private:

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

QgsRasterRenderer *QgsPalettedRendererWidget::renderer()
Expand Down Expand Up @@ -336,6 +337,36 @@ void QgsPalettedRendererWidget::loadColorTable()
}
}

void QgsPalettedRendererWidget::saveColorTable()
{
QgsSettings settings;
QString lastDir = settings.value( QStringLiteral( "lastColorMapDir" ), QDir::homePath() ).toString();
QString fileName = QFileDialog::getSaveFileName( this, tr( "Save file" ), lastDir, tr( "Text (*.clr)" ) );
if ( !fileName.isEmpty() )
{
if ( !fileName.endsWith( QLatin1String( ".clr" ), Qt::CaseInsensitive ) )
{
fileName = fileName + ".clr";
}

QFile outputFile( fileName );
if ( outputFile.open( QFile::WriteOnly | QIODevice::Truncate ) )
{
QTextStream outputStream( &outputFile );
outputStream << QgsPalettedRasterRenderer::classDataToString( mModel->classData() );
outputStream.flush();
outputFile.close();

QFileInfo fileInfo( fileName );
settings.setValue( QStringLiteral( "lastColorMapDir" ), fileInfo.absoluteDir().absolutePath() );
}
else
{
QMessageBox::warning( this, tr( "Write access denied" ), tr( "Write access denied. Adjust the file permissions and try again.\n\n" ) );
}
}
}

//
// QgsPalettedRendererModel
//
Expand Down
2 changes: 2 additions & 0 deletions src/gui/raster/qgspalettedrendererwidget.h
Expand Up @@ -108,6 +108,8 @@ class GUI_EXPORT QgsPalettedRendererWidget: public QgsRasterRendererWidget, priv
void changeLabel();
void applyColorRamp();
void loadColorTable();
void saveColorTable();

};

#endif // QGSPALETTEDRENDERERWIDGET_H
27 changes: 24 additions & 3 deletions tests/src/python/test_qgsrasterlayer.py
Expand Up @@ -480,6 +480,19 @@ def testLoadPalettedColorDataFromString(self):
self.assertEqual(classes[4].color.name(), '#0000ff')
self.assertEqual(classes[4].color.alpha(), 255)

# qgis style, with labels
qgis = '3 255 0 0 255 class 1\n4 0 255 0 200 class 2'
classes = QgsPalettedRasterRenderer.classDataFromString(qgis)
self.assertEqual(len(classes), 2)
self.assertEqual(classes[0].value, 3)
self.assertEqual(classes[0].color.name(), '#ff0000')
self.assertEqual(classes[0].color.alpha(), 255)
self.assertEqual(classes[0].label, 'class 1')
self.assertEqual(classes[1].value, 4)
self.assertEqual(classes[1].color.name(), '#00ff00')
self.assertEqual(classes[1].color.alpha(), 200)
self.assertEqual(classes[1].label, 'class 2')

# some bad inputs
bad = ''
classes = QgsPalettedRasterRenderer.classDataFromString(bad)
Expand All @@ -496,9 +509,6 @@ def testLoadPalettedColorDataFromString(self):
bad = '1 255 a 0'
classes = QgsPalettedRasterRenderer.classDataFromString(bad)
self.assertEqual(len(classes), 1)
bad = '1 255 255 0 0 0 0 0 0 0'
classes = QgsPalettedRasterRenderer.classDataFromString(bad)
self.assertEqual(len(classes), 0)

def testLoadPalettedClassDataFromFile(self):
# bad file
Expand Down Expand Up @@ -533,5 +543,16 @@ def testLoadPalettedClassDataFromFile(self):
self.assertEqual(classes[9].value, 10)
self.assertEqual(classes[9].color.name(), '#ffb600')

def testPalettedClassDataToString(self):
classes = [QgsPalettedRasterRenderer.Class(1, QColor(0, 255, 0), 'class 2'),
QgsPalettedRasterRenderer.Class(3, QColor(255, 0, 0), 'class 1')]
self.assertEqual(QgsPalettedRasterRenderer.classDataToString(classes), '1 0 255 0 255 class 2\n3 255 0 0 255 class 1')
# must be sorted by value to work OK in ArcMap
classes = [QgsPalettedRasterRenderer.Class(4, QColor(0, 255, 0), 'class 2'),
QgsPalettedRasterRenderer.Class(3, QColor(255, 0, 0), 'class 1')]
self.assertEqual(QgsPalettedRasterRenderer.classDataToString(classes),
'3 255 0 0 255 class 1\n4 0 255 0 255 class 2')


if __name__ == '__main__':
unittest.main()
10 changes: 10 additions & 0 deletions tests/testdata/raster/test.clr
@@ -0,0 +1,10 @@
1 0 0 0
2 200 200 200
3 0 110 0
4 110 65 0
5 0 0 255
6 0 89 255
7 0 174 255
8 0 255 246
9 238 255 0
10 255 182 0

0 comments on commit 7eb63d9

Please sign in to comment.