Handle other rat types

This commit is contained in:
Alessandro Pasotti 2022-10-19 19:34:20 +02:00 committed by Nyall Dawson
parent 62dff87ccf
commit 3479a66e4e
21 changed files with 636 additions and 316 deletions

View File

@ -70,11 +70,6 @@ Returns ``True`` if the field carries a color ramp component information (RedMin
Qgis::RasterAttributeTableType type() const;
%Docstring
Returns the Raster Attribute Table type.
%End
void setType( const Qgis::RasterAttributeTableType type );
%Docstring
Sets the Raster Attribute Table ``type``
%End
bool hasColor() const;
@ -223,6 +218,13 @@ Creates a new field from ``name``, ``usage`` and ``type`` and inserts it at ``po
bool insertColor( int position, QString *errorMessage /Out/ = 0 );
%Docstring
Create RGBA fields and inserts them at ``position``, optionally reporting any error in ``errorMessage``, returns ``True`` on success.
%End
bool setFieldUsage( int fieldIndex, const Qgis::RasterAttributeTableFieldUsage usage );
%Docstring
Change the usage of the field at index ``fieldIndex`` to ``usage`` with checks for allowed types.
:return: ``True`` on success.
%End
bool insertRamp( int position, QString *errorMessage /Out/ = 0 );
@ -336,15 +338,30 @@ the classification column based on the field usage.
QgsGradientColorRamp colorRamp( QStringList &labels /Out/, const int labelColumn = -1 ) const;
%Docstring
Returns the color ramp for an athematic Raster Attribute Table
returning the ``labels``, optionally generated from ``labelColumn``.
setting the labels in ``labels``, optionally generated from ``labelColumn``.
%End
QgsRasterRenderer *createRenderer( QgsRasterDataProvider *provider, const int bandNumber, const int classificationColumn = -1 ) /Factory/;
%Docstring
Creates and returns a (possibly ``None``) raster renderer for the
specified ``provider`` and ``bandNumber`` and optionally classified
specified ``provider`` and ``bandNumber`` and optionally reclassified
by ``classificationColumn``, the default value of -1 makes the method
guess the classification column based on the field usage.
.. note::
athematic attribute tables with color ramps cannot be reclassified,
the renderer will still use the ``classificationColumn`` for
generating the class labels.
%End
QList<QList<QVariant>> orderedRows( ) const;
%Docstring
Returns the data rows ordered by the value column(s) in ascending order, if
the attribute table type is athematic the middle value for each row range
is considered for ordering.
If the attribute table does not have any value field (and hence is not valid),
the current data are returned without any change.
%End
static Qgis::RasterAttributeTableFieldUsage guessFieldUsage( const QString &name, const QVariant::Type type );
@ -389,7 +406,6 @@ Returns information about supported Raster Attribute Table usages.
.. seealso:: :py:func:`usageName`
%End
static QHash<Qgis::RasterAttributeTableFieldUsage, QgsRasterAttributeTable::UsageInformation> sUsageInformation;
};

View File

@ -748,7 +748,7 @@ Loads the filesystem-based attribute table for the specified ``bandNumber`` from
.. versionadded:: 3.30
%End
virtual bool writeNativeAttributeTable( QString *errorMessage /Out/ = 0 ) const; //#spellok
virtual bool writeNativeAttributeTable( QString *errorMessage /Out/ = 0 ); //#spellok
virtual bool readNativeAttributeTable( QString *errorMessage /Out/ = 0 );
%Docstring

View File

@ -12061,10 +12061,11 @@ void QgisApp::createRasterAttributeTable()
layer->dataProvider()->setAttributeTable( bandNumber, rat );
// Save it
const QString filePath { dlg.filePath() };
if ( ! filePath.isEmpty() )
const bool saveToFile { dlg.saveToFile() };
if ( saveToFile )
{
if ( QMessageBox::question( nullptr, tr( "Confirm Overwrite" ), tr( "Are you sure you want to overwrite the existing attribute table at '%1'?" ).arg( filePath ), QMessageBox::Yes | QMessageBox::No, QMessageBox::No ) != QMessageBox::Yes )
const QString filePath { dlg.filePath() };
if ( QFile::exists( filePath ) && QMessageBox::question( nullptr, tr( "Confirm Overwrite" ), tr( "Are you sure you want to overwrite the existing attribute table at '%1'?" ).arg( filePath ), QMessageBox::Yes | QMessageBox::No, QMessageBox::No ) != QMessageBox::Yes )
{
return;
}

View File

@ -3114,63 +3114,6 @@ bool QgsGdalProvider::readNativeAttributeTable( QString *errorMessage )
usages.append( usage );
}
QStringList ratFieldNames { ratFields.names( ) };
QStringList lNames;
// Try to identify fields in case of Raster Attribute Table with wrong usages
if ( ! usages.contains( Qgis::RasterAttributeTableFieldUsage::MinMax ) &&
!( usages.contains( Qgis::RasterAttributeTableFieldUsage::Min ) && usages.contains( Qgis::RasterAttributeTableFieldUsage::Max ) ) )
{
if ( lowerNames.contains( QStringLiteral( "value" ) ) )
{
usages[ lowerNames.indexOf( QLatin1String( "value" ) ) ] = Qgis::RasterAttributeTableFieldUsage::MinMax;
}
else
{
const QStringList minValueNames { {
QStringLiteral( "min" ),
QStringLiteral( "min_value" ),
QStringLiteral( "min value" ),
QStringLiteral( "value min" ),
QStringLiteral( "value_min" ),
} };
for ( const QString &minName : std::as_const( minValueNames ) )
{
if ( lowerNames.contains( minName ) )
{
usages[ lowerNames.indexOf( minName ) ] = Qgis::RasterAttributeTableFieldUsage::Min;
break;
}
}
const QStringList maxValueNames { {
QStringLiteral( "max" ),
QStringLiteral( "max_value" ),
QStringLiteral( "max value" ),
QStringLiteral( "value max" ),
QStringLiteral( "value_max" ),
} };
for ( const QString &maxName : std::as_const( minValueNames ) )
{
if ( lowerNames.contains( maxName ) )
{
usages[ lowerNames.indexOf( maxName ) ] = Qgis::RasterAttributeTableFieldUsage::Max;
break;
}
}
}
}
if ( ! usages.contains( Qgis::RasterAttributeTableFieldUsage::PixelCount ) )
{
if ( lowerNames.contains( QStringLiteral( "count" ) ) )
{
usages[ lowerNames.indexOf( QLatin1String( "count" ) ) ] = Qgis::RasterAttributeTableFieldUsage::PixelCount;
}
}
std::unique_ptr<QgsRasterAttributeTable> rat = std::make_unique<QgsRasterAttributeTable>();
for ( const auto &field : std::as_const( ratFields ) )
@ -3208,33 +3151,58 @@ bool QgsGdalProvider::readNativeAttributeTable( QString *errorMessage )
rat->appendRow( rowData );
}
// Try to cope with invalid rats due to generic fields
if ( ! rat->isValid( ) )
{
std::unique_ptr<QgsRasterAttributeTable> ratCopy = std::make_unique<QgsRasterAttributeTable>( *rat );
bool changed { false };
for ( int fieldIdx = 0; fieldIdx < ratCopy->fields().count( ); ++fieldIdx )
{
const QgsRasterAttributeTable::Field field { ratCopy->fields().at( fieldIdx ) };
if ( field.usage == Qgis::RasterAttributeTableFieldUsage::Generic )
{
const Qgis::RasterAttributeTableFieldUsage newUsage { QgsRasterAttributeTable::guessFieldUsage( field.name, field.type ) };
if ( newUsage != Qgis::RasterAttributeTableFieldUsage::Generic && ratCopy->setFieldUsage( fieldIdx, newUsage ) )
{
changed = true;
}
}
}
// Did that work?
if ( changed && ratCopy->isValid( ) )
{
rat.reset( ratCopy.release() );
}
}
hasAtLeastOnedRat = rat->fields().count( ) > 0;
if ( hasAtLeastOnedRat )
{
rat->setType( static_cast<Qgis::RasterAttributeTableType>( GDALRATGetTableType( hRat ) ) );
rat->setDirty( false );
setAttributeTable( bandNumber, rat.release() );
}
else if ( errorMessage )
{
*errorMessage = QObject::tr( "Raster Attribute Table has no columns: skipping." );
*errorMessage = QObject::tr( "Raster attribute table has no columns: skipping." );
}
}
}
}
else if ( errorMessage )
{
*errorMessage = QObject::tr( "Dataset is not valid and Raster Attribute Table could not be loaded." );
*errorMessage = QObject::tr( "Dataset is not valid and raster attribute table could not be loaded." );
}
return hasAtLeastOnedRat;
}
bool QgsGdalProvider::writeNativeAttributeTable( QString *errorMessage ) const //#spellok
bool QgsGdalProvider::writeNativeAttributeTable( QString *errorMessage ) //#spellok
{
bool success { false };
bool wasReopenedReadWrite { false };
for ( int band = 1; band <= bandCount(); band++ )
{
QgsRasterAttributeTable *rat { attributeTable( band ) };
@ -3242,101 +3210,136 @@ bool QgsGdalProvider::writeNativeAttributeTable( QString *errorMessage ) const /
{
continue;
}
if ( rat->isDirty() )
// Needs to be in write mode for HFA and perhaps other formats!
if ( GDALGetAccess( mGdalDataset ) == GA_ReadOnly )
{
GDALRasterBandH hBand { GDALGetRasterBand( mGdalBaseDataset, band ) };
GDALRasterAttributeTableH hRat = GDALCreateRasterAttributeTable( );
if ( GDALRATSetTableType( hRat, static_cast<GDALRATTableType>( rat->type() ) ) != CE_None )
QgsDebugMsg( QStringLiteral( "re-opening the dataset in read/write mode" ) );
GDALClose( mGdalDataset );
mGdalBaseDataset = gdalOpen( dataSourceUri( true ), GDAL_OF_UPDATE );
// if the dataset couldn't be opened in read / write mode, tell the user
if ( !mGdalBaseDataset )
{
mGdalBaseDataset = gdalOpen( dataSourceUri( true ), GDAL_OF_READONLY );
//Since we are not a virtual warped dataset, mGdalDataSet and mGdalBaseDataset are supposed to be the same
mGdalDataset = mGdalBaseDataset;
if ( errorMessage )
{
*errorMessage = tr( "GDAL Error reopening dataset in write mode, raster attribute table could not be saved." );
}
return false;
}
wasReopenedReadWrite = true;
}
GDALRasterBandH hBand { GDALGetRasterBand( mGdalBaseDataset, band ) };
GDALRasterAttributeTableH hRat = GDALCreateRasterAttributeTable( );
if ( GDALRATSetTableType( hRat, static_cast<GDALRATTableType>( rat->type() ) ) != CE_None )
{
if ( errorMessage )
{
*errorMessage = QObject::tr( "GDAL error setting the table type, raster attribute table could not be saved." );
}
GDALDestroyRasterAttributeTable( hRat );
return false;
}
const QList<QgsRasterAttributeTable::Field> ratFields { rat->fields() };
QMap<int, GDALRATFieldType> typeMap;
int colIdx { 0 };
for ( const QgsRasterAttributeTable::Field &field : std::as_const( ratFields ) )
{
GDALRATFieldType fType { GFT_String };
switch ( field.type )
{
case QVariant::Int:
case QVariant::UInt:
case QVariant::LongLong:
case QVariant::ULongLong:
{
fType = GFT_Integer;
break;
}
case QVariant::Double:
{
fType = GFT_Real;
break;
}
default:
fType = GFT_String;
}
if ( GDALRATCreateColumn( hRat, field.name.toStdString().c_str(), fType, static_cast<GDALRATFieldUsage>( field.usage ) ) != CE_None )
{
if ( errorMessage )
{
*errorMessage = QObject::tr( "RAT table type could not be set, Raster Attribute Table was not saved (GDAL error)." );
*errorMessage = QObject::tr( "GDAL error creating column '%1, raster attribute table could not be saved." ).arg( field.name );
}
GDALDestroyRasterAttributeTable( hRat );
return false;
}
const QList<QgsRasterAttributeTable::Field> ratFields { rat->fields() };
QMap<int, GDALRATFieldType> typeMap;
typeMap[ colIdx ] = fType;
colIdx++;
}
int colIdx { 0 };
for ( const QgsRasterAttributeTable::Field &field : std::as_const( ratFields ) )
// Save data
const QList<QVariantList> data { rat->data() };
int rowIdx { 0 };
for ( const auto &row : std::as_const( data ) )
{
for ( int colIdx = 0; colIdx < row.size(); colIdx++ )
{
GDALRATFieldType fType { GFT_String };
switch ( field.type )
switch ( typeMap[ colIdx ] )
{
case QVariant::Int:
case QVariant::UInt:
case QVariant::LongLong:
case QVariant::ULongLong:
{
fType = GFT_Integer;
case GFT_Real:
GDALRATSetValueAsDouble( hRat, rowIdx, colIdx, row[ colIdx ].toDouble( ) );
break;
}
case QVariant::Double:
{
fType = GFT_Real;
case GFT_Integer:
GDALRATSetValueAsInt( hRat, rowIdx, colIdx, row[ colIdx ].toInt( ) );
break;
}
default:
fType = GFT_String;
GDALRATSetValueAsString( hRat, rowIdx, colIdx, row[ colIdx ].toString().toStdString().c_str() );
}
if ( GDALRATCreateColumn( hRat, field.name.toStdString().c_str(), fType, static_cast<GDALRATFieldUsage>( field.usage ) ) != CE_None )
{
if ( errorMessage )
{
*errorMessage = QObject::tr( "RAT column '%1 could not be created, Raster Attribute Table was not saved (GDAL error)." ).arg( field.name );
}
GDALDestroyRasterAttributeTable( hRat );
return false;
}
typeMap[ colIdx ] = fType;
colIdx++;
}
rowIdx++;
}
// Save data
const QList<QVariantList> data { rat->data() };
int rowIdx { 0 };
for ( const auto &row : std::as_const( data ) )
GDALRATSetTableType( hRat, static_cast<GDALRATTableType>( rat->type() ) );
if ( GDALSetDefaultRAT( hBand, hRat ) != CE_None )
{
if ( errorMessage )
{
for ( int colIdx = 0; colIdx < row.size(); colIdx++ )
{
switch ( typeMap[ colIdx ] )
{
case GFT_Real:
GDALRATSetValueAsDouble( hRat, rowIdx, colIdx, row[ colIdx ].toDouble( ) );
break;
case GFT_Integer:
GDALRATSetValueAsInt( hRat, rowIdx, colIdx, row[ colIdx ].toInt( ) );
break;
default:
GDALRATSetValueAsString( hRat, rowIdx, colIdx, row[ colIdx ].toString().toStdString().c_str() );
}
}
rowIdx++;
*errorMessage = tr( "GDAL error saving raster attribute table, raster attribute table could not be saved." );
}
GDALRATSetTableType( hRat, static_cast<GDALRATTableType>( rat->type() ) );
if ( GDALSetDefaultRAT( hBand, hRat ) != CE_None )
{
if ( errorMessage )
{
*errorMessage = QObject::tr( "RAT could not be saved (GDAL error)." );
}
GDALDestroyRasterAttributeTable( hRat );
return false;
}
GDALDestroyRasterAttributeTable( hRat );
success = false;
}
else
{
rat->setDirty( false );
GDALFlushCache( mGdalBaseDataset );
success = true;
}
else if ( ! rat->isDirty() && errorMessage )
{
*errorMessage = QObject::tr( "RAT has not modifications and was not saved." );
}
}
if ( wasReopenedReadWrite )
{
QgsDebugMsg( QStringLiteral( "re-opening the dataset in read-only mode" ) );
GDALClose( mGdalDataset );
mGdalBaseDataset = gdalOpen( dataSourceUri( true ), GDAL_OF_READONLY );
// if the dataset couldn't be opened in read / write mode, tell the user
if ( !mGdalBaseDataset )
{
mGdalBaseDataset = gdalOpen( dataSourceUri( true ), GDAL_OF_UPDATE );
//Since we are not a virtual warped dataset, mGdalDataSet and mGdalBaseDataset are supposed to be the same
mGdalDataset = mGdalBaseDataset;
}
}
return success;
}

View File

@ -231,7 +231,7 @@ class QgsGdalProvider final: public QgsRasterDataProvider, QgsGdalProviderBase
//! Load attribute tables
bool readNativeAttributeTable( QString *errorMessage = nullptr ) override;
bool writeNativeAttributeTable( QString *errorMessage = nullptr ) const override; //#spellok
bool writeNativeAttributeTable( QString *errorMessage = nullptr ) override; //#spellok
// There are 2 cloning mechanisms.
// * Either the cloned provider use the same GDAL handles as the main provider

View File

@ -36,10 +36,6 @@ Qgis::RasterAttributeTableType QgsRasterAttributeTable::type() const
return mType;
}
void QgsRasterAttributeTable::setType( const Qgis::RasterAttributeTableType type )
{
mType = type;
}
bool QgsRasterAttributeTable::hasColor() const
{
@ -324,13 +320,14 @@ bool QgsRasterAttributeTable::insertField( int position, const Field &field, QSt
// Set/change the table type from the value field type
if ( field.usage == Qgis::RasterAttributeTableFieldUsage::MinMax )
{
setType( Qgis::RasterAttributeTableType::Thematic );
mType = Qgis::RasterAttributeTableType::Thematic;
}
else if ( field.usage == Qgis::RasterAttributeTableFieldUsage::Max || field.usage == Qgis::RasterAttributeTableFieldUsage::Max )
{
setType( Qgis::RasterAttributeTableType::Athematic );
mType = Qgis::RasterAttributeTableType::Athematic;
}
setType();
setDirty( true );
return true;
@ -356,6 +353,25 @@ bool QgsRasterAttributeTable::insertColor( int position, QString *errorMessage )
return true;
}
bool QgsRasterAttributeTable::setFieldUsage( int fieldIndex, const Qgis::RasterAttributeTableFieldUsage usage )
{
if ( fieldIndex < 0 || fieldIndex >= fields().count( ) )
{
return false;
}
const Field field { fields().at( fieldIndex ) };
if ( ! usageInformation()[ usage ].allowedTypes.contains( field.type ) )
{
return false;
}
mFields[ fieldIndex ].usage = usage;
setType();
return true;
}
bool QgsRasterAttributeTable::insertRamp( int position, QString *errorMessage )
{
if ( mType != Qgis::RasterAttributeTableType::Athematic )
@ -402,6 +418,7 @@ bool QgsRasterAttributeTable::removeField( const QString &name, QString *errorMe
{
it->removeAt( idx );
}
setType();
setDirty( true );
return true;
}
@ -595,15 +612,6 @@ bool QgsRasterAttributeTable::readFromFile( const QString &path, QString *errorM
appendRow( f.attributes().toList() );
}
if ( usages().contains( Qgis::RasterAttributeTableFieldUsage::MinMax ) )
{
setType( Qgis::RasterAttributeTableType::Thematic );
}
else
{
setType( Qgis::RasterAttributeTableType::Athematic );
}
mFilePath = path;
setDirty( false );
@ -1042,7 +1050,6 @@ QgsRasterAttributeTable *QgsRasterAttributeTable::createFromRaster( QgsRasterLay
if ( const QgsPalettedRasterRenderer *palettedRenderer = dynamic_cast<const QgsPalettedRasterRenderer *>( renderer ) )
{
QgsRasterAttributeTable *rat = new QgsRasterAttributeTable();
rat->setType( Qgis::RasterAttributeTableType::Thematic );
rat->appendField( QStringLiteral( "Value" ), Qgis::RasterAttributeTableFieldUsage::MinMax, QVariant::Type::Double );
rat->appendField( QStringLiteral( "Class" ), Qgis::RasterAttributeTableFieldUsage::Name, QVariant::Type::String );
rat->appendField( QStringLiteral( "Red" ), Qgis::RasterAttributeTableFieldUsage::Red, QVariant::Type::Int );
@ -1071,38 +1078,92 @@ QgsRasterAttributeTable *QgsRasterAttributeTable::createFromRaster( QgsRasterLay
if ( const QgsColorRampShader *shaderFunction = dynamic_cast<const QgsColorRampShader *>( shader->rasterShaderFunction() ) )
{
QgsRasterAttributeTable *rat = new QgsRasterAttributeTable();
rat->setType( Qgis::RasterAttributeTableType::Athematic );
rat->appendField( QStringLiteral( "Min" ), Qgis::RasterAttributeTableFieldUsage::Min, QVariant::Type::Double );
rat->appendField( QStringLiteral( "Max" ), Qgis::RasterAttributeTableFieldUsage::Max, QVariant::Type::Double );
rat->appendField( QStringLiteral( "Class" ), Qgis::RasterAttributeTableFieldUsage::Name, QVariant::Type::String );
rat->appendField( QStringLiteral( "RedMin" ), Qgis::RasterAttributeTableFieldUsage::RedMin, QVariant::Type::Int );
rat->appendField( QStringLiteral( "GreenMin" ), Qgis::RasterAttributeTableFieldUsage::GreenMin, QVariant::Type::Int );
rat->appendField( QStringLiteral( "BlueMin" ), Qgis::RasterAttributeTableFieldUsage::BlueMin, QVariant::Type::Int );
rat->appendField( QStringLiteral( "AlphaMin" ), Qgis::RasterAttributeTableFieldUsage::AlphaMin, QVariant::Type::Int );
rat->appendField( QStringLiteral( "RedMax" ), Qgis::RasterAttributeTableFieldUsage::RedMax, QVariant::Type::Int );
rat->appendField( QStringLiteral( "GreenMax" ), Qgis::RasterAttributeTableFieldUsage::GreenMax, QVariant::Type::Int );
rat->appendField( QStringLiteral( "BlueMax" ), Qgis::RasterAttributeTableFieldUsage::BlueMax, QVariant::Type::Int );
rat->appendField( QStringLiteral( "AlphaMax" ), Qgis::RasterAttributeTableFieldUsage::AlphaMax, QVariant::Type::Int );
const QList<QgsColorRampShader::ColorRampItem> rampItems { shaderFunction->colorRampItemList() };
if ( rampItems.count( ) > 1 )
switch ( shaderFunction->colorRampType() )
{
QColor color1 { rampItems.at( 0 ).color };
QString label1 { rampItems.at( 0 ).label };
QVariant value1( rampItems.at( 0 ).value );
for ( int i = 1; i < rampItems.count( ); ++i )
case QgsColorRampShader::Type::Interpolated:
{
const QgsColorRampShader::ColorRampItem &rampItem { rampItems.at( i )};
rat->appendRow( QVariantList() << value1 << rampItem.value << QStringLiteral( "%1 - %2" ).arg( label1, rampItem.label ) << 0 << 0 << 0 << 255 << 0 << 0 << 0 << 255 );
rat->setRamp( rat->data().length() - 1, color1, rampItem.color );
label1 = rampItem.label;
value1 = rampItem.value;
color1 = rampItem.color;
rat->appendField( QStringLiteral( "Min" ), Qgis::RasterAttributeTableFieldUsage::Min, QVariant::Type::Double );
rat->appendField( QStringLiteral( "Max" ), Qgis::RasterAttributeTableFieldUsage::Max, QVariant::Type::Double );
rat->appendField( QStringLiteral( "Class" ), Qgis::RasterAttributeTableFieldUsage::Name, QVariant::Type::String );
rat->appendField( QStringLiteral( "RedMin" ), Qgis::RasterAttributeTableFieldUsage::RedMin, QVariant::Type::Int );
rat->appendField( QStringLiteral( "GreenMin" ), Qgis::RasterAttributeTableFieldUsage::GreenMin, QVariant::Type::Int );
rat->appendField( QStringLiteral( "BlueMin" ), Qgis::RasterAttributeTableFieldUsage::BlueMin, QVariant::Type::Int );
rat->appendField( QStringLiteral( "AlphaMin" ), Qgis::RasterAttributeTableFieldUsage::AlphaMin, QVariant::Type::Int );
rat->appendField( QStringLiteral( "RedMax" ), Qgis::RasterAttributeTableFieldUsage::RedMax, QVariant::Type::Int );
rat->appendField( QStringLiteral( "GreenMax" ), Qgis::RasterAttributeTableFieldUsage::GreenMax, QVariant::Type::Int );
rat->appendField( QStringLiteral( "BlueMax" ), Qgis::RasterAttributeTableFieldUsage::BlueMax, QVariant::Type::Int );
rat->appendField( QStringLiteral( "AlphaMax" ), Qgis::RasterAttributeTableFieldUsage::AlphaMax, QVariant::Type::Int );
const QList<QgsColorRampShader::ColorRampItem> rampItems { shaderFunction->colorRampItemList() };
if ( rampItems.count( ) > 1 )
{
QColor color1 { rampItems.at( 0 ).color };
QString label1 { rampItems.at( 0 ).label };
QVariant value1( rampItems.at( 0 ).value );
for ( int i = 1; i < rampItems.count( ); ++i )
{
const QgsColorRampShader::ColorRampItem &rampItem { rampItems.at( i )};
rat->appendRow( QVariantList() << value1 << rampItem.value << QStringLiteral( "%1 - %2" ).arg( label1, rampItem.label ) << 0 << 0 << 0 << 255 << 0 << 0 << 0 << 255 );
rat->setRamp( rat->data().length() - 1, color1, rampItem.color );
label1 = rampItem.label;
value1 = rampItem.value;
color1 = rampItem.color;
}
}
break;
}
case QgsColorRampShader::Type::Discrete:
{
rat->appendField( QStringLiteral( "Min" ), Qgis::RasterAttributeTableFieldUsage::Min, QVariant::Type::Double );
rat->appendField( QStringLiteral( "Max" ), Qgis::RasterAttributeTableFieldUsage::Max, QVariant::Type::Double );
rat->appendField( QStringLiteral( "Class" ), Qgis::RasterAttributeTableFieldUsage::Name, QVariant::Type::String );
rat->appendField( QStringLiteral( "Red" ), Qgis::RasterAttributeTableFieldUsage::Red, QVariant::Type::Int );
rat->appendField( QStringLiteral( "Green" ), Qgis::RasterAttributeTableFieldUsage::Green, QVariant::Type::Int );
rat->appendField( QStringLiteral( "Blue" ), Qgis::RasterAttributeTableFieldUsage::Blue, QVariant::Type::Int );
rat->appendField( QStringLiteral( "Alpha" ), Qgis::RasterAttributeTableFieldUsage::Alpha, QVariant::Type::Int );
const QList<QgsColorRampShader::ColorRampItem> rampItems { shaderFunction->colorRampItemList() };
if ( rampItems.count( ) > 1 )
{
QColor color1 { rampItems.at( 0 ).color };
QString label1 { rampItems.at( 0 ).label };
QVariant value1( rampItems.at( 0 ).value );
for ( int i = 1; i < rampItems.count( ); ++i )
{
const QgsColorRampShader::ColorRampItem &rampItem { rampItems.at( i )};
rat->appendRow( QVariantList() << value1 << rampItem.value << QStringLiteral( "%1 - %2" ).arg( label1, rampItem.label ) << 0 << 0 << 0 << 255 << 0 << 0 << 0 << 255 );
rat->setRamp( rat->data().length() - 1, color1, rampItem.color );
label1 = rampItem.label;
value1 = rampItem.value;
color1 = rampItem.color;
}
}
break;
}
case QgsColorRampShader::Type::Exact:
{
rat->appendField( QStringLiteral( "Value" ), Qgis::RasterAttributeTableFieldUsage::MinMax, QVariant::Type::Double );
rat->appendField( QStringLiteral( "Class" ), Qgis::RasterAttributeTableFieldUsage::Name, QVariant::Type::String );
rat->appendField( QStringLiteral( "Red" ), Qgis::RasterAttributeTableFieldUsage::Red, QVariant::Type::Int );
rat->appendField( QStringLiteral( "Green" ), Qgis::RasterAttributeTableFieldUsage::Green, QVariant::Type::Int );
rat->appendField( QStringLiteral( "Blue" ), Qgis::RasterAttributeTableFieldUsage::Blue, QVariant::Type::Int );
rat->appendField( QStringLiteral( "Alpha" ), Qgis::RasterAttributeTableFieldUsage::Alpha, QVariant::Type::Int );
const QList<QgsColorRampShader::ColorRampItem> rampItems { shaderFunction->colorRampItemList() };
for ( const QgsColorRampShader::ColorRampItem &rampItem : std::as_const( rampItems ) )
{
rat->appendRow( QVariantList() << rampItem.value << rampItem.label << 0 << 0 << 0 << 255 );
rat->setColor( rat->data().length() - 1, rampItem.color );
}
break;
}
}
if ( bandNumber )
{
*bandNumber = pseudoColorRenderer->band();
}
return rat;
}
else
@ -1149,6 +1210,12 @@ QHash<Qgis::RasterAttributeTableFieldUsage, QgsRasterAttributeTable::UsageInform
return QgsRasterAttributeTable::sUsageInformation;
}
void QgsRasterAttributeTable::setType()
{
const QList<Qgis::RasterAttributeTableFieldUsage> fieldUsages { usages() };
mType = fieldUsages.contains( Qgis::RasterAttributeTableFieldUsage::MinMax ) ? Qgis::RasterAttributeTableType::Thematic : Qgis::RasterAttributeTableType::Athematic;
}
QHash<int, QgsRasterAttributeTable::UsageInformation> QgsRasterAttributeTable::usageInformationInt()
{
QHash<int, QgsRasterAttributeTable::UsageInformation> usageInfoInt;
@ -1275,11 +1342,7 @@ QgsGradientColorRamp QgsRasterAttributeTable::colorRamp( QStringList &labels, co
if ( ! isnan( min ) && ! isnan( max ) )
{
QList<QVariantList> dataCopy { mData };
std::sort( dataCopy.begin(), dataCopy.end(), [ & ]( const QVariantList & first, const QVariantList & second ) -> bool
{
return ( first.at( maxIdx ).toDouble() + first.at( minIdx ).toDouble() ) / 2 < ( second.at( maxIdx ).toDouble() + second.at( minIdx ).toDouble() ) / 2;
} );
const QList<QVariantList> dataCopy( orderedRows() );
QgsRasterAttributeTable orderedRat;
for ( const Field &f : std::as_const( mFields ) )
@ -1336,7 +1399,7 @@ QgsGradientColorRamp QgsRasterAttributeTable::colorRamp( QStringList &labels, co
};
// Case 1: range classes, discrete colors
// Create stops for the min value of each class except for the first.
// Create stops for the lower value of each class except for the first.
if ( orderedRat.hasColor() && isRange )
{
labels.push_back( labelFromField( 0 ) );
@ -1350,57 +1413,47 @@ QgsGradientColorRamp QgsRasterAttributeTable::colorRamp( QStringList &labels, co
}
}
// Case 2: range classes, gradients colors
// Take the class borders (average value between max of previous class and min of the next)
// Take the class bounds (average value between max of previous class and min of the next)
// to avoid potential overlapping or gaps between classes.
// Create stop:
// first stop at value taking the max color of the previous class
// second stop at value + epsilon taking the min color of the next class
// second stop at value + epsilon taking the min color of the next class, unless colors and offset are equal
else if ( orderedRat.hasRamp() && isRange )
{
double prevOffset { 0 };
labels.push_back( labelFromField( 0 ) );
for ( int rowIdx = 1; rowIdx < orderedRat.data().count(); ++rowIdx )
{
labels.push_back( labelFromField( rowIdx ) );
const int prevRowIdx { rowIdx - 1 };
const double offset { ( ( orderedRat.value( rowIdx, minIdx ).toDouble( ) + orderedRat.value( prevRowIdx, maxIdx ).toDouble( ) ) / 2.0 - min ) / range };
const QgsGradientColorRamp previousRamp { orderedRat.ramp( prevRowIdx ) };
stops.append( QgsGradientStop( offset, previousRamp.color2() ) );
const QgsGradientColorRamp currentRamp { orderedRat.ramp( rowIdx ) };
stops.append( QgsGradientStop( offset + std::numeric_limits<double>::epsilon(), currentRamp.color1() ) );
// An additional stop is added if the colors are different or offsets are different by 1e-6 (offset varies from 0 to 1).
if ( currentRamp.color1() != previousRamp.color2() && qgsDoubleNear( offset, prevOffset, 1e-6 ) )
{
stops.append( QgsGradientStop( offset + std::numeric_limits<double>::epsilon(), currentRamp.color1() ) );
}
prevOffset = offset;
}
}
// Case 3: range classes but no colors at all
// Take the class borders (average value between max of previous class and min of the next)
// Create stop for the lower class, actually skipping the upper bound of the last class
else
{
labels.push_back( labelFromField( 0 ) );
for ( int rowIdx = 1; rowIdx < orderedRat.data().count(); ++rowIdx )
{
const int prevRowIdx { rowIdx - 1 };
const double offset { ( ( orderedRat.value( rowIdx, minIdx ).toDouble( ) + orderedRat.value( prevRowIdx, maxIdx ).toDouble( ) ) / 2.0 - min ) / range };
stops.append( QgsGradientStop( offset, ramp.color( offset ) ) );
labels.push_back( labelFromField( rowIdx ) );
}
}
#if 0
// Collect stops and colors
for ( int rowIdx = 0; rowIdx < orderedRat.data().count(); ++rowIdx )
{
const double offset { ( orderedRat.value( rowIdx, maxIdx ).toDouble( ) - min ) / range };
if ( hasColor( ) && rowIdx < orderedRat.data().count() - 1 )
{
const QColor color { orderedRat.color( rowIdx ) };
stops.append( QgsGradientStop( offset, color ) );
}
else if ( hasRamp() && rowIdx < orderedRat.data().count() - 1 )
{
const QColor color1 { orderedRat.ramp( rowIdx ).color1() };
if ( color1 != lastColor )
{
stops.append( QgsGradientStop( lastOffset + std::numeric_limits<double>::epsilon(), color1 ) );
}
const QColor color2 { orderedRat.ramp( rowIdx ).color2() };
lastColor = color2;
stops.append( QgsGradientStop( offset, color1 ) );
}
else if ( ! hasColor() && ! hasRamp() )
{
stops.push_back( QgsGradientStop( offset, ramp.color( offset ) ) );
}
lastOffset = offset;
}
#endif
}
}
}
@ -1461,9 +1514,6 @@ QgsRasterRenderer *QgsRasterAttributeTable::createRenderer( QgsRasterDataProvide
// Set labels
if ( QgsColorRampShader *shaderFunction = static_cast<QgsColorRampShader *>( pseudoColorRenderer->shader()->rasterShaderFunction() ) )
{
QgsColorRampLegendNodeSettings *settings = new QgsColorRampLegendNodeSettings( *( shaderFunction->legendSettings() ) );
settings->setUseContinuousLegend( false );
shaderFunction->setLegendSettings( settings );
shaderFunction->setMinimumValue( minValue() );
shaderFunction->setMaximumValue( maxValue() );
const QList<QgsColorRampShader::ColorRampItem> itemList { shaderFunction->colorRampItemList() };
@ -1471,9 +1521,11 @@ QgsRasterRenderer *QgsRasterAttributeTable::createRenderer( QgsRasterDataProvide
if ( ! itemList.isEmpty() )
{
QList<QgsColorRampShader::ColorRampItem> deduplicatedDitemList;
// Deduplicate entries, this is necessary because the ramp for athematic
// Deduplicate entries, this is necessary when the ramp for athematic
// ramp rasters creates a stop for each class limit (min and max) which
// should be coincident with the previous class max and next class min.
// may be coincident with the previous class max and next class min.
// When this happens and the colors for the previous class max and
// next class min are equal we can squash the legend classes.
QgsColorRampShader::ColorRampItem item { itemList.first() };
if ( labelsAreUsable )
{
@ -1504,6 +1556,40 @@ QgsRasterRenderer *QgsRasterAttributeTable::createRenderer( QgsRasterDataProvide
return renderer;
}
QList<QList<QVariant> > QgsRasterAttributeTable::orderedRows() const
{
const QList<Qgis::RasterAttributeTableFieldUsage> fieldUsages { usages() };
const int minIdx { fieldUsages.indexOf( Qgis::RasterAttributeTableFieldUsage::Min ) };
const int maxIdx { fieldUsages.indexOf( Qgis::RasterAttributeTableFieldUsage::Max ) };
const bool isRange { minIdx >= 0 && maxIdx >= 0 };
QList<QVariantList> dataCopy( mData );
if ( isRange )
{
std::sort( dataCopy.begin(), dataCopy.end(), [ & ]( const QVariantList & first, const QVariantList & second ) -> bool
{
return ( first.at( maxIdx ).toDouble() + first.at( minIdx ).toDouble() ) < ( second.at( maxIdx ).toDouble() + second.at( minIdx ).toDouble() );
} );
}
else
{
const int minMaxIdx { fieldUsages.indexOf( Qgis::RasterAttributeTableFieldUsage::MinMax ) };
if ( minMaxIdx < 0 )
{
return dataCopy;
}
else
{
std::sort( dataCopy.begin(), dataCopy.end(), [ & ]( const QVariantList & first, const QVariantList & second ) -> bool
{
return first.at( minMaxIdx ).toDouble() < second.at( minMaxIdx ).toDouble();
} );
}
}
return dataCopy;
}
bool QgsRasterAttributeTable::Field::isColor() const
{
return usage == Qgis::RasterAttributeTableFieldUsage::Red || usage == Qgis::RasterAttributeTableFieldUsage::Green || usage == Qgis::RasterAttributeTableFieldUsage::Blue || usage == Qgis::RasterAttributeTableFieldUsage::Alpha;

View File

@ -104,11 +104,6 @@ class CORE_EXPORT QgsRasterAttributeTable
*/
Qgis::RasterAttributeTableType type() const;
/**
* Sets the Raster Attribute Table \a type
*/
void setType( const Qgis::RasterAttributeTableType type );
/**
* Returns TRUE if the Raster Attribute Table has color RGBA information.
* \see color()
@ -230,6 +225,12 @@ class CORE_EXPORT QgsRasterAttributeTable
*/
bool insertColor( int position, QString *errorMessage SIP_OUT = nullptr );
/**
* Change the usage of the field at index \a fieldIndex to \a usage with checks for allowed types.
* \return TRUE on success.
*/
bool setFieldUsage( int fieldIndex, const Qgis::RasterAttributeTableFieldUsage usage );
/**
* Create RGBA minimum and maximum fields and inserts them at \a position, optionally reporting any error in \a errorMessage, returns TRUE on success.
*/
@ -331,18 +332,31 @@ class CORE_EXPORT QgsRasterAttributeTable
/**
* Returns the color ramp for an athematic Raster Attribute Table
* returning the \a labels, optionally generated from \a labelColumn.
* setting the labels in \a labels, optionally generated from \a labelColumn.
*/
QgsGradientColorRamp colorRamp( QStringList &labels SIP_OUT, const int labelColumn = -1 ) const;
/**
* Creates and returns a (possibly NULLPTR) raster renderer for the
* specified \a provider and \a bandNumber and optionally classified
* specified \a provider and \a bandNumber and optionally reclassified
* by \a classificationColumn, the default value of -1 makes the method
* guess the classification column based on the field usage.
*
* \note athematic attribute tables with color ramps cannot be reclassified,
* the renderer will still use the \a classificationColumn for
* generating the class labels.
*/
QgsRasterRenderer *createRenderer( QgsRasterDataProvider *provider, const int bandNumber, const int classificationColumn = -1 ) SIP_FACTORY;
/**
* Returns the data rows ordered by the value column(s) in ascending order, if
* the attribute table type is athematic the middle value for each row range
* is considered for ordering.
* If the attribute table does not have any value field (and hence is not valid),
* the current data are returned without any change.
*/
QList<QList<QVariant>> orderedRows( ) const;
/**
* Try to determine the field usage from its \a name and \a type.
*/
@ -387,7 +401,7 @@ class CORE_EXPORT QgsRasterAttributeTable
*/
static QHash<int, QgsRasterAttributeTable::UsageInformation> usageInformationInt( ) SIP_PYNAME( usageInformation );
static QHash<Qgis::RasterAttributeTableFieldUsage, QgsRasterAttributeTable::UsageInformation> sUsageInformation;
static QHash<Qgis::RasterAttributeTableFieldUsage, QgsRasterAttributeTable::UsageInformation> sUsageInformation SIP_SKIP;
///@encond
@ -399,6 +413,9 @@ class CORE_EXPORT QgsRasterAttributeTable
bool mIsDirty;
QString mFilePath;
// Set type from fields.
void setType( );
};
#endif // QGSRASTERATTRIBUTETABLE_H

View File

@ -714,7 +714,7 @@ bool QgsRasterDataProvider::readFileBasedAttributeTable( int bandNumber, const Q
}
}
bool QgsRasterDataProvider::writeNativeAttributeTable( QString *errorMessage ) const //#spellok
bool QgsRasterDataProvider::writeNativeAttributeTable( QString *errorMessage ) //#spellok
{
Q_UNUSED( errorMessage );
return false;

View File

@ -793,7 +793,7 @@ class CORE_EXPORT QgsRasterDataProvider : public QgsDataProvider, public QgsRast
* \note No checks for Raster Attribute Table validity are performed when saving, it is client code responsibility to handle validation.
* \since QGIS 3.30
*/
virtual bool writeNativeAttributeTable( QString *errorMessage SIP_OUT = nullptr ) const; //#spellok
virtual bool writeNativeAttributeTable( QString *errorMessage SIP_OUT = nullptr ); //#spellok
/**
* Reads the native attribute table, optionally reporting any error in \a errorMessage, returns TRUE on success.

View File

@ -149,11 +149,17 @@ QgsRasterRenderer *QgsRasterRendererRegistry::defaultRendererForDrawingStyle( Qg
const int grayBand = 1;
// If the raster band has an attribute table try to use it.
if ( QgsRasterAttributeTable *rat = provider->attributeTable( grayBand ) )
QString ratErrorMessage;
if ( QgsRasterAttributeTable *rat = provider->attributeTable( grayBand ); rat && rat->isValid( &ratErrorMessage ) )
{
renderer = rat->createRenderer( provider, grayBand );
}
if ( ! ratErrorMessage.isEmpty() )
{
QgsDebugMsgLevel( QStringLiteral( "Invalid RAT from band 1, RAT was not used to create the renderer: %1." ).arg( ratErrorMessage ), 2 );
}
if ( ! renderer )
{
renderer = new QgsSingleBandGrayRenderer( provider, grayBand );

View File

@ -3,6 +3,7 @@ set(QGIS_GUI_SRCS
raster/qgsrasterattributetablewidget.cpp
raster/qgsrasterattributetabledialog.cpp
raster/qgsrasterattributetableaddcolumndialog.cpp
raster/qgsrasterattributetableaddrowdialog.cpp
raster/qgscolorrampshaderwidget.cpp
raster/qgsmultibandcolorrendererwidget.cpp
raster/qgspalettedrendererwidget.cpp
@ -1287,6 +1288,7 @@ set(QGIS_GUI_HDRS
raster/qgscreaterasterattributetabledialog.h
raster/qgsrasterattributetabledialog.h
raster/qgsrasterattributetableaddcolumndialog.h
raster/qgsrasterattributetableaddrowdialog.h
raster/qgscolorrampshaderwidget.h
raster/qgshillshaderendererwidget.h
raster/qgsmultibandcolorrendererwidget.h
@ -1451,6 +1453,7 @@ set(QGIS_GUI_UI_HDRS
${CMAKE_CURRENT_BINARY_DIR}/../ui/ui_qgsqueryresultwidgetbase.h
${CMAKE_CURRENT_BINARY_DIR}/../ui/ui_qgsrasterattributetablewidgetbase.h
${CMAKE_CURRENT_BINARY_DIR}/../ui/ui_qgsrasterattributetabledialogbase.h
${CMAKE_CURRENT_BINARY_DIR}/../ui/ui_qgsrasterattributetableaddrowdialogbase.h
${CMAKE_CURRENT_BINARY_DIR}/../ui/ui_qgscreaterasterattributetabledialogbase.h
${CMAKE_CURRENT_BINARY_DIR}/../ui/ui_qgsrasterattributetableaddcolumndialog.h
${CMAKE_CURRENT_BINARY_DIR}/../ui/ui_qgssqlcomposerdialogbase.h

View File

@ -25,24 +25,13 @@ QgsCreateRasterAttributeTableDialog::QgsCreateRasterAttributeTableDialog( QgsRas
setupUi( this );
const bool nativeRatSupported { mRasterLayer->dataProvider()->providerCapabilities().testFlag( QgsRasterDataProvider::ProviderCapability::NativeRasterAttributeTable ) };
// Apparently, some drivers (HFA) ignore Min/Max fields and set them to generic,
// for this reason we disable the native support for thematic RATs (later in the loop)
bool nativeRatSupported { mRasterLayer->dataProvider()->providerCapabilities().testFlag( QgsRasterDataProvider::ProviderCapability::NativeRasterAttributeTable ) };
connect( mNativeRadioButton, &QRadioButton::toggled, this, &QgsCreateRasterAttributeTableDialog::updateButtons );
connect( mDbfRadioButton, &QRadioButton::toggled, this, &QgsCreateRasterAttributeTableDialog::updateButtons );
if ( ! nativeRatSupported )
{
mNativeRadioButton->setEnabled( false );
mDbfRadioButton->setChecked( true );
}
else
{
mDbfPathWidget->setFilter( QStringLiteral( "VAT DBF Files (*.vat.dbf)" ) );
if ( QFile::exists( mRasterLayer->dataProvider()->dataSourceUri( ) ) )
{
mDbfPathWidget->setFilePath( mRasterLayer->dataProvider()->dataSourceUri( ) + ".vat.dbf" );
}
}
// Check for existing rats
QStringList existingRatsInfo;
@ -52,6 +41,12 @@ QgsCreateRasterAttributeTableDialog::QgsCreateRasterAttributeTableDialog( QgsRas
{
if ( QgsRasterAttributeTable *rat = mRasterLayer->attributeTable( bandNo ) )
{
// disable the native support for thematic RATs
if ( nativeRatSupported && rat->type() != Qgis::RasterAttributeTableType::Athematic )
{
nativeRatSupported = false;
existingRatsInfo.push_back( tr( "The data provider supports attribute table storage but some drivers do not support 'thematic' types, for this reason the option is disabled." ) );
}
if ( ! rat->filePath().isEmpty() )
{
existingRatsInfo.push_back( tr( "Raster band %1 already has an associated attribute table at %2." ).arg( QString::number( bandNo ), rat->filePath() ) );
@ -67,10 +62,27 @@ QgsCreateRasterAttributeTableDialog::QgsCreateRasterAttributeTableDialog( QgsRas
if ( ! existingRatsInfo.isEmpty() )
{
mCreateInfoLabel->setText( mCreateInfoLabel->text().append( QStringLiteral( "<br><ul><li>" ) + existingRatsInfo.join( QStringLiteral( "</li><li>" ) ) ).append( QStringLiteral( "</ul>" ) ) );
mCreateInfoLabel->show();
}
if ( ! nativeRatSupported )
{
mNativeRadioButton->setEnabled( false );
mDbfRadioButton->setChecked( true );
}
else
{
mDbfPathWidget->setFilter( QStringLiteral( "VAT DBF Files (*.vat.dbf)" ) );
if ( QFile::exists( mRasterLayer->dataProvider()->dataSourceUri( ) ) )
{
mDbfPathWidget->setFilePath( mRasterLayer->dataProvider()->dataSourceUri( ) + ".vat.dbf" );
}
}
connect( mButtonBox, &QDialogButtonBox::accepted, this, &QDialog::accept );
connect( mButtonBox, &QDialogButtonBox::rejected, this, &QDialog::reject );
updateButtons();
}
QString QgsCreateRasterAttributeTableDialog::filePath() const
@ -78,6 +90,11 @@ QString QgsCreateRasterAttributeTableDialog::filePath() const
return mDbfPathWidget->filePath();
}
bool QgsCreateRasterAttributeTableDialog::saveToFile() const
{
return mDbfRadioButton->isChecked();
}
bool QgsCreateRasterAttributeTableDialog::openWhenDone() const
{
return mOpenRat->isChecked();

View File

@ -48,6 +48,11 @@ class GUI_EXPORT QgsCreateRasterAttributeTableDialog : public QDialog, private U
*/
QString filePath( ) const;
/**
* Returns TRUE if the option to save to a file is selected.
*/
bool saveToFile( ) const;
/**
* Returns TRUE if the option to open the newly created attribute table is checked.
*/

View File

@ -23,11 +23,20 @@
class QgsRasterAttributeTable;
/**
* The QgsRasterAttributeTableAddColumnDialog class collects options to add a new column to a raster attribute table.
* \since QGIS 3.30
*/
class GUI_EXPORT QgsRasterAttributeTableAddColumnDialog : public QDialog, private Ui::QgsRasterAttributeTableAddColumnDialogBase
{
Q_OBJECT
public:
/**
* Creates a new QgsRasterAttributeTableAddColumnDialog
* \param attributeTable the raster attribute table
* \param parent optional parent
*/
QgsRasterAttributeTableAddColumnDialog( QgsRasterAttributeTable *attributeTable, QWidget *parent SIP_TRANSFERTHIS = nullptr );
/**

View File

@ -0,0 +1,30 @@
/***************************************************************************
qgsrasterattributetableaddrowdialog.cpp - QgsRasterAttributeTableAddRowDialog
---------------------
begin : 18.10.2022
copyright : (C) 2022 by ale
email : [your-email-here]
***************************************************************************
* *
* 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 "qgsrasterattributetableaddrowdialog.h"
#include "qgsgui.h"
QgsRasterAttributeTableAddRowDialog::QgsRasterAttributeTableAddRowDialog( QWidget *parent )
: QDialog( parent )
{
setupUi( this );
QgsGui::enableAutoGeometryRestore( this );
}
bool QgsRasterAttributeTableAddRowDialog::insertAfter() const
{
return mAfter->isChecked();
}

View File

@ -0,0 +1,48 @@
/***************************************************************************
qgsrasterattributetableaddrowdialog.h - QgsRasterAttributeTableAddRowDialog
---------------------
begin : 18.10.2022
copyright : (C) 2022 by Alessandro Pasotti
email : elpaso at itopen dot it
***************************************************************************
* *
* 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. *
* *
***************************************************************************/
#ifndef QGSRASTERATTRIBUTETABLEADDROWDIALOG_H
#define QGSRASTERATTRIBUTETABLEADDROWDIALOG_H
#include <QDialog>
#include "qgis_gui.h"
#include "qgis.h"
#include "ui_qgsrasterattributetableaddrowdialogbase.h"
/**
* The QgsRasterAttributeTableAddColumnDialog class collects options to add a new row to a raster attribute table.
* \since QGIS 3.30
*/
class GUI_EXPORT QgsRasterAttributeTableAddRowDialog : public QDialog, private Ui::QgsRasterAttributeTableAddRowDialogBase
{
Q_OBJECT
public:
/**
* Creates a new QgsRasterAttributeTableAddRowDialog
* \param attributeTable the raster attribute table
* \param parent optional parent
*/
QgsRasterAttributeTableAddRowDialog( QWidget *parent SIP_TRANSFERTHIS = nullptr );
/**
* Returns TRUE if the desired insertion position for the new row is after the currently selected row, FALSE if the insertion point is before.
*/
bool insertAfter() const;
};
#endif // QGSRASTERATTRIBUTETABLEADDROWDIALOG_H

View File

@ -15,6 +15,7 @@
***************************************************************************/
#include "qgsrasterattributetablemodel.h"
#include <QColor>
#include <QFont>
QString QgsRasterAttributeTableModel::RAT_COLOR_HEADER_NAME = QObject::tr( "Color" );
@ -392,6 +393,12 @@ QVariant QgsRasterAttributeTableModel::data( const QModelIndex &index, int role
{
return mRat->data().at( index.row() ).at( index.column() );
}
else if ( role == Qt::ItemDataRole::FontRole && ( field.isColor() || field.isRamp() ) )
{
QFont font;
font.setItalic( true );
return font;
}
}
return QVariant();
}

View File

@ -19,6 +19,7 @@
#include "qgsapplication.h"
#include "qgsmessagebar.h"
#include "qgsrasterattributetableaddcolumndialog.h"
#include "qgsrasterattributetableaddrowdialog.h"
#include "qgscolorbutton.h"
#include "qgsgradientcolorrampdialog.h"
#include "qgspalettedrasterrenderer.h"
@ -99,7 +100,6 @@ void QgsRasterAttributeTableWidget::init( int bandNumber )
mAttributeTable = nullptr;
mCurrentBand = 0;
mRasterBandsComboBox->clear();
mClassifyComboBox->clear();
QList<int> availableRats;
@ -141,42 +141,18 @@ void QgsRasterAttributeTableWidget::init( int bandNumber )
updateButtons();
} );
connect( mModel.get(), &QgsRasterAttributeTableModel::columnsInserted, this, [ = ]( const QModelIndex &, int, int )
{
setDelegates();
} );
connect( mModel.get(), &QgsRasterAttributeTableModel::columnsRemoved, this, [ = ]( const QModelIndex &, int, int )
{
setDelegates();
} );
static_cast<QSortFilterProxyModel *>( mRATView->model() )->setSourceModel( mModel.get() );
const QList<QgsRasterAttributeTable::Field> tableFields { mAttributeTable->fields() };
int fieldIdx { 0 };
const QHash<Qgis::RasterAttributeTableFieldUsage, QgsRasterAttributeTable::UsageInformation> usageInfo { QgsRasterAttributeTable::usageInformation() };
for ( const QgsRasterAttributeTable::Field &f : std::as_const( tableFields ) )
{
if ( usageInfo[f.usage].maybeClass )
{
mClassifyComboBox->addItem( QgsFields::iconForFieldType( f.type ), f.name, QVariant( fieldIdx ) );
}
fieldIdx++;
}
if ( mAttributeTable->hasColor() )
{
if ( mAttributeTable->usages().contains( Qgis::RasterAttributeTableFieldUsage::Alpha ) )
{
mRATView->setItemDelegateForColumn( mAttributeTable->fields().count( ), new ColorAlphaDelegate( mRATView ) );
}
else
{
mRATView->setItemDelegateForColumn( mAttributeTable->fields().count( ), new ColorDelegate( mRATView ) );
}
}
else if ( mAttributeTable->hasRamp() )
{
if ( mAttributeTable->usages().contains( Qgis::RasterAttributeTableFieldUsage::AlphaMin ) )
{
mRATView->setItemDelegateForColumn( mAttributeTable->fields().count( ), new ColorRampAlphaDelegate( mRATView ) );
}
else
{
mRATView->setItemDelegateForColumn( mAttributeTable->fields().count( ), new ColorRampDelegate( mRATView ) );
}
}
setDelegates();
}
else
{
@ -250,7 +226,7 @@ void QgsRasterAttributeTableWidget::saveChanges()
return;
}
}
else if ( nativeRatSupported )
else if ( newPath.isEmpty() )
{
saveToNative = true;
}
@ -371,8 +347,29 @@ void QgsRasterAttributeTableWidget::addRow()
{
if ( mAttributeTable )
{
// Default to append
int position { mModel->rowCount( QModelIndex() ) };
const QModelIndex currentIndex { mProxyModel->mapToSource( mRATView->selectionModel()->currentIndex() ) };
// If there is a selected row, ask if before of after.
if ( currentIndex.isValid() )
{
// Ask the user where to insert the new row (before or after the currently
// selected row).
QgsRasterAttributeTableAddRowDialog dlg;
if ( dlg.exec() != QDialog::DialogCode::Accepted )
{
return;
}
else
{
position = currentIndex.row() + ( dlg.insertAfter() ? 1 : 0 );
}
}
bool result { true };
QString errorMessage;
QVariantList rowData;
QList<QgsRasterAttributeTable::Field> fields { mAttributeTable->fields() };
@ -381,19 +378,16 @@ void QgsRasterAttributeTableWidget::addRow()
rowData.push_back( QVariant( field.type ) );
}
const QModelIndex currentIndex { mProxyModel->mapToSource( mRATView->selectionModel()->currentIndex() ) };
if ( currentIndex.isValid() )
{
result = mModel->insertRow( currentIndex.row(), rowData, &errorMessage );
}
else
{
result = mModel->insertRow( mModel->rowCount( QModelIndex() ), rowData, &errorMessage );
}
result = mModel->insertRow( position, rowData, &errorMessage );
if ( ! result )
{
notify( tr( "Error adding row" ), errorMessage, Qgis::MessageLevel::Critical );
}
else
{
mRATView->scrollTo( mRATView->model()->index( position, 0 ) );
}
}
}
@ -452,6 +446,50 @@ void QgsRasterAttributeTableWidget::notify( const QString &title, const QString
}
}
void QgsRasterAttributeTableWidget::setDelegates()
{
mClassifyComboBox->clear();
if ( mAttributeTable )
{
const QList<QgsRasterAttributeTable::Field> tableFields { mAttributeTable->fields() };
int fieldIdx { 0 };
const QHash<Qgis::RasterAttributeTableFieldUsage, QgsRasterAttributeTable::UsageInformation> usageInfo { QgsRasterAttributeTable::usageInformation() };
for ( const QgsRasterAttributeTable::Field &f : std::as_const( tableFields ) )
{
// Clear all delegates.
mRATView->setItemDelegateForColumn( fieldIdx, nullptr );
if ( usageInfo[f.usage].maybeClass )
{
mClassifyComboBox->addItem( QgsFields::iconForFieldType( f.type ), f.name, QVariant( fieldIdx ) );
}
fieldIdx++;
}
if ( mAttributeTable->hasColor() )
{
if ( mAttributeTable->usages().contains( Qgis::RasterAttributeTableFieldUsage::Alpha ) )
{
mRATView->setItemDelegateForColumn( mAttributeTable->fields().count( ), new ColorAlphaDelegate( mRATView ) );
}
else
{
mRATView->setItemDelegateForColumn( mAttributeTable->fields().count( ), new ColorDelegate( mRATView ) );
}
}
else if ( mAttributeTable->hasRamp() )
{
if ( mAttributeTable->usages().contains( Qgis::RasterAttributeTableFieldUsage::AlphaMin ) )
{
mRATView->setItemDelegateForColumn( mAttributeTable->fields().count( ), new ColorRampAlphaDelegate( mRATView ) );
}
else
{
mRATView->setItemDelegateForColumn( mAttributeTable->fields().count( ), new ColorRampDelegate( mRATView ) );
}
}
}
}
///@cond private

View File

@ -164,6 +164,7 @@ class GUI_EXPORT QgsRasterAttributeTableWidget : public QWidget, private Ui::Qgs
void removeRow();
void bandChanged( const int index );
void notify( const QString &title, const QString &message, Qgis::MessageLevel level = Qgis::MessageLevel::Info );
void setDelegates( );
};

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>336</width>
<height>206</height>
<width>391</width>
<height>222</height>
</rect>
</property>
<property name="windowTitle">
@ -17,7 +17,7 @@
<item>
<widget class="QLabel" name="mCreateInfoLabel">
<property name="text">
<string>&lt;p&gt;Create a new Raster Attribute Table (RAT) using the current symbology.&lt;/p&gt;</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Create a new Raster Attribute Table (RAT) from the current symbology.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="wordWrap">
<bool>true</bool>
@ -27,23 +27,23 @@
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Raster Attribute Table Destination</string>
<string>Raster Attribute Table Storage</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QRadioButton" name="mNativeRadioButton">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This method will save the attribute table using the data provider, &lt;/p&gt;&lt;p&gt;overwriting any existing attribute table for the raster band used&lt;/p&gt;&lt;p&gt;by the current style.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Depending on the data provider and the raster file format, the &lt;/p&gt;&lt;p&gt;attribute table will be embedded in the main raster file or saved&lt;/p&gt;&lt;p&gt; into a sidecar file manged by the data provider.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Data provider</string>
<string>Managed by the data provider</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<item row="1" column="0">
<widget class="QRadioButton" name="mDbfRadioButton">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This method will save the attribute table into a sidecar VAT.DBF file.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;The resulting file will not be associated with any particular band.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
@ -53,8 +53,15 @@
</property>
</widget>
</item>
<item>
<widget class="QgsFileWidget" name="mDbfPathWidget" native="true"/>
<item row="2" column="0">
<widget class="QgsFileWidget" name="mDbfPathWidget" native="true">
<property name="minimumSize">
<size>
<width>180</width>
<height>24</height>
</size>
</property>
</widget>
</item>
</layout>
</widget>

View File

@ -730,9 +730,9 @@ class TestQgsRasterAttributeTable(unittest.TestCase):
rat.appendField(QgsRasterAttributeTable.Field('BlueMax', Qgis.RasterAttributeTableFieldUsage.BlueMax, QVariant.Int))
data_rows = [
[2.599999, 2.99999, 'two.5-three', 0, 255, 0, 255, 0, 0],
[0, 1.99999, 'zero-two', 255, 0, 0, 0, 255, 0],
[2, 2.5, 'two-two.5', 0, 255, 0, 0, 0, 255],
[2.50000000001, 3, 'two.5-three', 0, 255, 0, 255, 0, 0],
[0, 2, 'zero-two', 255, 0, 0, 0, 255, 0],
[2.00000000001, 2.5, 'two-two.5', 0, 255, 0, 0, 0, 255],
]
for row in data_rows:
@ -752,7 +752,7 @@ class TestQgsRasterAttributeTable(unittest.TestCase):
shader = renderer.shader()
func = shader.rasterShaderFunction()
self.assertEqual([(int(100 * st.offset), st.color.name()) for st in func.sourceColorRamp().stops()], [(66, '#00ff00'), (66, '#00ff00'), (85, '#0000ff'), (85, '#00ff00')]
self.assertEqual([(int(100 * st.offset), st.color.name()) for st in func.sourceColorRamp().stops()], [(66, '#00ff00'), (83, '#0000ff')]
)
# Test range classes and discrete colors on float
@ -777,6 +777,15 @@ class TestQgsRasterAttributeTable(unittest.TestCase):
rat.appendRow(row)
self.assertTrue(rat.isValid()[0])
# Test ordered
ordered = rat.orderedRows()
self.assertEqual(ordered, [
[-1e+25, 3000000000000, 'red class', 'class 0', 255, 0, 0],
[3000000000000, 1e+20, 'blue class', 'class 1', 0, 0, 255],
[1e+20, 5e+25, 'green class', 'class 2', 0, 255, 0],
])
raster.dataProvider().setAttributeTable(1, rat)
ok, errors = raster.dataProvider().writeNativeAttributeTable() # spellok
self.assertTrue(ok)
@ -788,6 +797,23 @@ class TestQgsRasterAttributeTable(unittest.TestCase):
func = shader.rasterShaderFunction()
self.assertEqual([i[0] for i in renderer.legendSymbologyItems()], ['red class', 'blue class', 'green class'])
# Test color less athematic RAT
rat = raster.attributeTable(1)
self.assertTrue(rat.removeField('Red')[0])
self.assertTrue(rat.removeField('Green')[0])
self.assertTrue(rat.removeField('Blue')[0])
self.assertFalse(rat.hasColor())
self.assertFalse(rat.hasRamp())
ramp = rat.colorRamp()
ramp, labels = rat.colorRamp()
self.assertEqual(len(ramp.stops()) + 1, len(labels))
self.assertEqual(labels, ['-1e+25 - 3e+12', '3e+12 - 1e+20', '1e+20 - 5e+25'])
ramp, labels = rat.colorRamp(2)
self.assertEqual(len(ramp.stops()) + 1, len(labels))
self.assertEqual(labels, ['red class', 'blue class', 'green class'])
if __name__ == '__main__':
unittest.main()