Skip to content

Commit 3479a66

Browse files
elpasonyalldawson
authored andcommittedNov 8, 2022
Handle other rat types
1 parent 62dff87 commit 3479a66

21 files changed

+622
-302
lines changed
 

‎python/core/auto_generated/raster/qgsrasterattributetable.sip.in

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,6 @@ Returns ``True`` if the field carries a color ramp component information (RedMin
7070
Qgis::RasterAttributeTableType type() const;
7171
%Docstring
7272
Returns the Raster Attribute Table type.
73-
%End
74-
75-
void setType( const Qgis::RasterAttributeTableType type );
76-
%Docstring
77-
Sets the Raster Attribute Table ``type``
7873
%End
7974

8075
bool hasColor() const;
@@ -223,6 +218,13 @@ Creates a new field from ``name``, ``usage`` and ``type`` and inserts it at ``po
223218
bool insertColor( int position, QString *errorMessage /Out/ = 0 );
224219
%Docstring
225220
Create RGBA fields and inserts them at ``position``, optionally reporting any error in ``errorMessage``, returns ``True`` on success.
221+
%End
222+
223+
bool setFieldUsage( int fieldIndex, const Qgis::RasterAttributeTableFieldUsage usage );
224+
%Docstring
225+
Change the usage of the field at index ``fieldIndex`` to ``usage`` with checks for allowed types.
226+
227+
:return: ``True`` on success.
226228
%End
227229

228230
bool insertRamp( int position, QString *errorMessage /Out/ = 0 );
@@ -336,15 +338,30 @@ the classification column based on the field usage.
336338
QgsGradientColorRamp colorRamp( QStringList &labels /Out/, const int labelColumn = -1 ) const;
337339
%Docstring
338340
Returns the color ramp for an athematic Raster Attribute Table
339-
returning the ``labels``, optionally generated from ``labelColumn``.
341+
setting the labels in ``labels``, optionally generated from ``labelColumn``.
340342
%End
341343

342344
QgsRasterRenderer *createRenderer( QgsRasterDataProvider *provider, const int bandNumber, const int classificationColumn = -1 ) /Factory/;
343345
%Docstring
344346
Creates and returns a (possibly ``None``) raster renderer for the
345-
specified ``provider`` and ``bandNumber`` and optionally classified
347+
specified ``provider`` and ``bandNumber`` and optionally reclassified
346348
by ``classificationColumn``, the default value of -1 makes the method
347349
guess the classification column based on the field usage.
350+
351+
.. note::
352+
353+
athematic attribute tables with color ramps cannot be reclassified,
354+
the renderer will still use the ``classificationColumn`` for
355+
generating the class labels.
356+
%End
357+
358+
QList<QList<QVariant>> orderedRows( ) const;
359+
%Docstring
360+
Returns the data rows ordered by the value column(s) in ascending order, if
361+
the attribute table type is athematic the middle value for each row range
362+
is considered for ordering.
363+
If the attribute table does not have any value field (and hence is not valid),
364+
the current data are returned without any change.
348365
%End
349366

350367
static Qgis::RasterAttributeTableFieldUsage guessFieldUsage( const QString &name, const QVariant::Type type );
@@ -389,7 +406,6 @@ Returns information about supported Raster Attribute Table usages.
389406
.. seealso:: :py:func:`usageName`
390407
%End
391408

392-
static QHash<Qgis::RasterAttributeTableFieldUsage, QgsRasterAttributeTable::UsageInformation> sUsageInformation;
393409

394410

395411
};

‎python/core/auto_generated/raster/qgsrasterdataprovider.sip.in

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -748,7 +748,7 @@ Loads the filesystem-based attribute table for the specified ``bandNumber`` from
748748
.. versionadded:: 3.30
749749
%End
750750

751-
virtual bool writeNativeAttributeTable( QString *errorMessage /Out/ = 0 ) const; //#spellok
751+
virtual bool writeNativeAttributeTable( QString *errorMessage /Out/ = 0 ); //#spellok
752752

753753
virtual bool readNativeAttributeTable( QString *errorMessage /Out/ = 0 );
754754
%Docstring

‎src/app/qgisapp.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12061,10 +12061,11 @@ void QgisApp::createRasterAttributeTable()
1206112061
layer->dataProvider()->setAttributeTable( bandNumber, rat );
1206212062

1206312063
// Save it
12064-
const QString filePath { dlg.filePath() };
12065-
if ( ! filePath.isEmpty() )
12064+
const bool saveToFile { dlg.saveToFile() };
12065+
if ( saveToFile )
1206612066
{
12067-
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 )
12067+
const QString filePath { dlg.filePath() };
12068+
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 )
1206812069
{
1206912070
return;
1207012071
}

‎src/core/providers/gdal/qgsgdalprovider.cpp

Lines changed: 128 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -3114,63 +3114,6 @@ bool QgsGdalProvider::readNativeAttributeTable( QString *errorMessage )
31143114
usages.append( usage );
31153115
}
31163116

3117-
QStringList ratFieldNames { ratFields.names( ) };
3118-
QStringList lNames;
3119-
3120-
// Try to identify fields in case of Raster Attribute Table with wrong usages
3121-
if ( ! usages.contains( Qgis::RasterAttributeTableFieldUsage::MinMax ) &&
3122-
!( usages.contains( Qgis::RasterAttributeTableFieldUsage::Min ) && usages.contains( Qgis::RasterAttributeTableFieldUsage::Max ) ) )
3123-
{
3124-
if ( lowerNames.contains( QStringLiteral( "value" ) ) )
3125-
{
3126-
usages[ lowerNames.indexOf( QLatin1String( "value" ) ) ] = Qgis::RasterAttributeTableFieldUsage::MinMax;
3127-
}
3128-
else
3129-
{
3130-
const QStringList minValueNames { {
3131-
QStringLiteral( "min" ),
3132-
QStringLiteral( "min_value" ),
3133-
QStringLiteral( "min value" ),
3134-
QStringLiteral( "value min" ),
3135-
QStringLiteral( "value_min" ),
3136-
} };
3137-
3138-
for ( const QString &minName : std::as_const( minValueNames ) )
3139-
{
3140-
if ( lowerNames.contains( minName ) )
3141-
{
3142-
usages[ lowerNames.indexOf( minName ) ] = Qgis::RasterAttributeTableFieldUsage::Min;
3143-
break;
3144-
}
3145-
}
3146-
3147-
const QStringList maxValueNames { {
3148-
QStringLiteral( "max" ),
3149-
QStringLiteral( "max_value" ),
3150-
QStringLiteral( "max value" ),
3151-
QStringLiteral( "value max" ),
3152-
QStringLiteral( "value_max" ),
3153-
} };
3154-
3155-
for ( const QString &maxName : std::as_const( minValueNames ) )
3156-
{
3157-
if ( lowerNames.contains( maxName ) )
3158-
{
3159-
usages[ lowerNames.indexOf( maxName ) ] = Qgis::RasterAttributeTableFieldUsage::Max;
3160-
break;
3161-
}
3162-
}
3163-
}
3164-
}
3165-
3166-
if ( ! usages.contains( Qgis::RasterAttributeTableFieldUsage::PixelCount ) )
3167-
{
3168-
if ( lowerNames.contains( QStringLiteral( "count" ) ) )
3169-
{
3170-
usages[ lowerNames.indexOf( QLatin1String( "count" ) ) ] = Qgis::RasterAttributeTableFieldUsage::PixelCount;
3171-
}
3172-
}
3173-
31743117
std::unique_ptr<QgsRasterAttributeTable> rat = std::make_unique<QgsRasterAttributeTable>();
31753118

31763119
for ( const auto &field : std::as_const( ratFields ) )
@@ -3208,135 +3151,195 @@ bool QgsGdalProvider::readNativeAttributeTable( QString *errorMessage )
32083151
rat->appendRow( rowData );
32093152
}
32103153

3154+
// Try to cope with invalid rats due to generic fields
3155+
if ( ! rat->isValid( ) )
3156+
{
3157+
std::unique_ptr<QgsRasterAttributeTable> ratCopy = std::make_unique<QgsRasterAttributeTable>( *rat );
3158+
bool changed { false };
3159+
for ( int fieldIdx = 0; fieldIdx < ratCopy->fields().count( ); ++fieldIdx )
3160+
{
3161+
const QgsRasterAttributeTable::Field field { ratCopy->fields().at( fieldIdx ) };
3162+
if ( field.usage == Qgis::RasterAttributeTableFieldUsage::Generic )
3163+
{
3164+
const Qgis::RasterAttributeTableFieldUsage newUsage { QgsRasterAttributeTable::guessFieldUsage( field.name, field.type ) };
3165+
if ( newUsage != Qgis::RasterAttributeTableFieldUsage::Generic && ratCopy->setFieldUsage( fieldIdx, newUsage ) )
3166+
{
3167+
changed = true;
3168+
}
3169+
}
3170+
}
3171+
3172+
// Did that work?
3173+
if ( changed && ratCopy->isValid( ) )
3174+
{
3175+
rat.reset( ratCopy.release() );
3176+
}
3177+
}
3178+
32113179
hasAtLeastOnedRat = rat->fields().count( ) > 0;
32123180

32133181
if ( hasAtLeastOnedRat )
32143182
{
3215-
rat->setType( static_cast<Qgis::RasterAttributeTableType>( GDALRATGetTableType( hRat ) ) );
32163183
rat->setDirty( false );
32173184
setAttributeTable( bandNumber, rat.release() );
32183185
}
32193186
else if ( errorMessage )
32203187
{
3221-
*errorMessage = QObject::tr( "Raster Attribute Table has no columns: skipping." );
3188+
*errorMessage = QObject::tr( "Raster attribute table has no columns: skipping." );
32223189
}
32233190
}
32243191
}
32253192
}
32263193
else if ( errorMessage )
32273194
{
3228-
*errorMessage = QObject::tr( "Dataset is not valid and Raster Attribute Table could not be loaded." );
3195+
*errorMessage = QObject::tr( "Dataset is not valid and raster attribute table could not be loaded." );
32293196
}
32303197

32313198
return hasAtLeastOnedRat;
32323199
}
32333200

32343201

3235-
bool QgsGdalProvider::writeNativeAttributeTable( QString *errorMessage ) const //#spellok
3202+
bool QgsGdalProvider::writeNativeAttributeTable( QString *errorMessage ) //#spellok
32363203
{
32373204
bool success { false };
3205+
bool wasReopenedReadWrite { false };
32383206
for ( int band = 1; band <= bandCount(); band++ )
32393207
{
32403208
QgsRasterAttributeTable *rat { attributeTable( band ) };
32413209
if ( ! rat )
32423210
{
32433211
continue;
32443212
}
3245-
if ( rat->isDirty() )
3213+
3214+
// Needs to be in write mode for HFA and perhaps other formats!
3215+
if ( GDALGetAccess( mGdalDataset ) == GA_ReadOnly )
32463216
{
3247-
GDALRasterBandH hBand { GDALGetRasterBand( mGdalBaseDataset, band ) };
3248-
GDALRasterAttributeTableH hRat = GDALCreateRasterAttributeTable( );
3249-
if ( GDALRATSetTableType( hRat, static_cast<GDALRATTableType>( rat->type() ) ) != CE_None )
3217+
QgsDebugMsg( QStringLiteral( "re-opening the dataset in read/write mode" ) );
3218+
GDALClose( mGdalDataset );
3219+
3220+
mGdalBaseDataset = gdalOpen( dataSourceUri( true ), GDAL_OF_UPDATE );
3221+
3222+
// if the dataset couldn't be opened in read / write mode, tell the user
3223+
if ( !mGdalBaseDataset )
32503224
{
3225+
mGdalBaseDataset = gdalOpen( dataSourceUri( true ), GDAL_OF_READONLY );
3226+
//Since we are not a virtual warped dataset, mGdalDataSet and mGdalBaseDataset are supposed to be the same
3227+
mGdalDataset = mGdalBaseDataset;
32513228
if ( errorMessage )
32523229
{
3253-
*errorMessage = QObject::tr( "RAT table type could not be set, Raster Attribute Table was not saved (GDAL error)." );
3230+
*errorMessage = tr( "GDAL Error reopening dataset in write mode, raster attribute table could not be saved." );
32543231
}
3255-
GDALDestroyRasterAttributeTable( hRat );
32563232
return false;
32573233
}
3258-
const QList<QgsRasterAttributeTable::Field> ratFields { rat->fields() };
3259-
QMap<int, GDALRATFieldType> typeMap;
3234+
wasReopenedReadWrite = true;
3235+
}
3236+
3237+
GDALRasterBandH hBand { GDALGetRasterBand( mGdalBaseDataset, band ) };
3238+
GDALRasterAttributeTableH hRat = GDALCreateRasterAttributeTable( );
3239+
if ( GDALRATSetTableType( hRat, static_cast<GDALRATTableType>( rat->type() ) ) != CE_None )
3240+
{
3241+
if ( errorMessage )
3242+
{
3243+
*errorMessage = QObject::tr( "GDAL error setting the table type, raster attribute table could not be saved." );
3244+
}
3245+
GDALDestroyRasterAttributeTable( hRat );
3246+
return false;
3247+
}
3248+
const QList<QgsRasterAttributeTable::Field> ratFields { rat->fields() };
3249+
QMap<int, GDALRATFieldType> typeMap;
32603250

3261-
int colIdx { 0 };
3262-
for ( const QgsRasterAttributeTable::Field &field : std::as_const( ratFields ) )
3251+
int colIdx { 0 };
3252+
for ( const QgsRasterAttributeTable::Field &field : std::as_const( ratFields ) )
3253+
{
3254+
GDALRATFieldType fType { GFT_String };
3255+
switch ( field.type )
32633256
{
3264-
GDALRATFieldType fType { GFT_String };
3265-
switch ( field.type )
3257+
case QVariant::Int:
3258+
case QVariant::UInt:
3259+
case QVariant::LongLong:
3260+
case QVariant::ULongLong:
32663261
{
3267-
case QVariant::Int:
3268-
case QVariant::UInt:
3269-
case QVariant::LongLong:
3270-
case QVariant::ULongLong:
3271-
{
3272-
fType = GFT_Integer;
3273-
break;
3274-
}
3275-
case QVariant::Double:
3276-
{
3277-
fType = GFT_Real;
3278-
break;
3279-
}
3280-
default:
3281-
fType = GFT_String;
3262+
fType = GFT_Integer;
3263+
break;
32823264
}
3283-
if ( GDALRATCreateColumn( hRat, field.name.toStdString().c_str(), fType, static_cast<GDALRATFieldUsage>( field.usage ) ) != CE_None )
3265+
case QVariant::Double:
32843266
{
3285-
if ( errorMessage )
3286-
{
3287-
*errorMessage = QObject::tr( "RAT column '%1 could not be created, Raster Attribute Table was not saved (GDAL error)." ).arg( field.name );
3288-
}
3289-
GDALDestroyRasterAttributeTable( hRat );
3290-
return false;
3267+
fType = GFT_Real;
3268+
break;
32913269
}
3292-
typeMap[ colIdx ] = fType;
3293-
colIdx++;
3270+
default:
3271+
fType = GFT_String;
32943272
}
3295-
3296-
// Save data
3297-
const QList<QVariantList> data { rat->data() };
3298-
int rowIdx { 0 };
3299-
for ( const auto &row : std::as_const( data ) )
3273+
if ( GDALRATCreateColumn( hRat, field.name.toStdString().c_str(), fType, static_cast<GDALRATFieldUsage>( field.usage ) ) != CE_None )
33003274
{
3301-
for ( int colIdx = 0; colIdx < row.size(); colIdx++ )
3275+
if ( errorMessage )
33023276
{
3303-
switch ( typeMap[ colIdx ] )
3304-
{
3305-
case GFT_Real:
3306-
GDALRATSetValueAsDouble( hRat, rowIdx, colIdx, row[ colIdx ].toDouble( ) );
3307-
break;
3308-
case GFT_Integer:
3309-
GDALRATSetValueAsInt( hRat, rowIdx, colIdx, row[ colIdx ].toInt( ) );
3310-
break;
3311-
default:
3312-
GDALRATSetValueAsString( hRat, rowIdx, colIdx, row[ colIdx ].toString().toStdString().c_str() );
3313-
}
3277+
*errorMessage = QObject::tr( "GDAL error creating column '%1, raster attribute table could not be saved." ).arg( field.name );
33143278
}
3315-
rowIdx++;
3279+
GDALDestroyRasterAttributeTable( hRat );
3280+
return false;
33163281
}
3282+
typeMap[ colIdx ] = fType;
3283+
colIdx++;
3284+
}
33173285

3318-
GDALRATSetTableType( hRat, static_cast<GDALRATTableType>( rat->type() ) );
3319-
3320-
if ( GDALSetDefaultRAT( hBand, hRat ) != CE_None )
3286+
// Save data
3287+
const QList<QVariantList> data { rat->data() };
3288+
int rowIdx { 0 };
3289+
for ( const auto &row : std::as_const( data ) )
3290+
{
3291+
for ( int colIdx = 0; colIdx < row.size(); colIdx++ )
33213292
{
3322-
if ( errorMessage )
3293+
switch ( typeMap[ colIdx ] )
33233294
{
3324-
*errorMessage = QObject::tr( "RAT could not be saved (GDAL error)." );
3295+
case GFT_Real:
3296+
GDALRATSetValueAsDouble( hRat, rowIdx, colIdx, row[ colIdx ].toDouble( ) );
3297+
break;
3298+
case GFT_Integer:
3299+
GDALRATSetValueAsInt( hRat, rowIdx, colIdx, row[ colIdx ].toInt( ) );
3300+
break;
3301+
default:
3302+
GDALRATSetValueAsString( hRat, rowIdx, colIdx, row[ colIdx ].toString().toStdString().c_str() );
33253303
}
3326-
GDALDestroyRasterAttributeTable( hRat );
3327-
return false;
33283304
}
3305+
rowIdx++;
3306+
}
3307+
3308+
GDALRATSetTableType( hRat, static_cast<GDALRATTableType>( rat->type() ) );
33293309

3310+
if ( GDALSetDefaultRAT( hBand, hRat ) != CE_None )
3311+
{
3312+
if ( errorMessage )
3313+
{
3314+
*errorMessage = tr( "GDAL error saving raster attribute table, raster attribute table could not be saved." );
3315+
}
3316+
GDALDestroyRasterAttributeTable( hRat );
3317+
success = false;
3318+
}
3319+
else
3320+
{
33303321
rat->setDirty( false );
33313322
GDALFlushCache( mGdalBaseDataset );
33323323
success = true;
3333-
33343324
}
3335-
else if ( ! rat->isDirty() && errorMessage )
3325+
}
3326+
3327+
if ( wasReopenedReadWrite )
3328+
{
3329+
QgsDebugMsg( QStringLiteral( "re-opening the dataset in read-only mode" ) );
3330+
GDALClose( mGdalDataset );
3331+
3332+
mGdalBaseDataset = gdalOpen( dataSourceUri( true ), GDAL_OF_READONLY );
3333+
3334+
// if the dataset couldn't be opened in read / write mode, tell the user
3335+
if ( !mGdalBaseDataset )
33363336
{
3337-
*errorMessage = QObject::tr( "RAT has not modifications and was not saved." );
3337+
mGdalBaseDataset = gdalOpen( dataSourceUri( true ), GDAL_OF_UPDATE );
3338+
//Since we are not a virtual warped dataset, mGdalDataSet and mGdalBaseDataset are supposed to be the same
3339+
mGdalDataset = mGdalBaseDataset;
33383340
}
33393341
}
3342+
33403343
return success;
33413344
}
33423345

‎src/core/providers/gdal/qgsgdalprovider.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ class QgsGdalProvider final: public QgsRasterDataProvider, QgsGdalProviderBase
231231
//! Load attribute tables
232232
bool readNativeAttributeTable( QString *errorMessage = nullptr ) override;
233233

234-
bool writeNativeAttributeTable( QString *errorMessage = nullptr ) const override; //#spellok
234+
bool writeNativeAttributeTable( QString *errorMessage = nullptr ) override; //#spellok
235235

236236
// There are 2 cloning mechanisms.
237237
// * Either the cloned provider use the same GDAL handles as the main provider

‎src/core/raster/qgsrasterattributetable.cpp

Lines changed: 168 additions & 82 deletions
Large diffs are not rendered by default.

‎src/core/raster/qgsrasterattributetable.h

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -104,11 +104,6 @@ class CORE_EXPORT QgsRasterAttributeTable
104104
*/
105105
Qgis::RasterAttributeTableType type() const;
106106

107-
/**
108-
* Sets the Raster Attribute Table \a type
109-
*/
110-
void setType( const Qgis::RasterAttributeTableType type );
111-
112107
/**
113108
* Returns TRUE if the Raster Attribute Table has color RGBA information.
114109
* \see color()
@@ -230,6 +225,12 @@ class CORE_EXPORT QgsRasterAttributeTable
230225
*/
231226
bool insertColor( int position, QString *errorMessage SIP_OUT = nullptr );
232227

228+
/**
229+
* Change the usage of the field at index \a fieldIndex to \a usage with checks for allowed types.
230+
* \return TRUE on success.
231+
*/
232+
bool setFieldUsage( int fieldIndex, const Qgis::RasterAttributeTableFieldUsage usage );
233+
233234
/**
234235
* Create RGBA minimum and maximum fields and inserts them at \a position, optionally reporting any error in \a errorMessage, returns TRUE on success.
235236
*/
@@ -331,18 +332,31 @@ class CORE_EXPORT QgsRasterAttributeTable
331332

332333
/**
333334
* Returns the color ramp for an athematic Raster Attribute Table
334-
* returning the \a labels, optionally generated from \a labelColumn.
335+
* setting the labels in \a labels, optionally generated from \a labelColumn.
335336
*/
336337
QgsGradientColorRamp colorRamp( QStringList &labels SIP_OUT, const int labelColumn = -1 ) const;
337338

338339
/**
339340
* Creates and returns a (possibly NULLPTR) raster renderer for the
340-
* specified \a provider and \a bandNumber and optionally classified
341+
* specified \a provider and \a bandNumber and optionally reclassified
341342
* by \a classificationColumn, the default value of -1 makes the method
342343
* guess the classification column based on the field usage.
344+
*
345+
* \note athematic attribute tables with color ramps cannot be reclassified,
346+
* the renderer will still use the \a classificationColumn for
347+
* generating the class labels.
343348
*/
344349
QgsRasterRenderer *createRenderer( QgsRasterDataProvider *provider, const int bandNumber, const int classificationColumn = -1 ) SIP_FACTORY;
345350

351+
/**
352+
* Returns the data rows ordered by the value column(s) in ascending order, if
353+
* the attribute table type is athematic the middle value for each row range
354+
* is considered for ordering.
355+
* If the attribute table does not have any value field (and hence is not valid),
356+
* the current data are returned without any change.
357+
*/
358+
QList<QList<QVariant>> orderedRows( ) const;
359+
346360
/**
347361
* Try to determine the field usage from its \a name and \a type.
348362
*/
@@ -387,7 +401,7 @@ class CORE_EXPORT QgsRasterAttributeTable
387401
*/
388402
static QHash<int, QgsRasterAttributeTable::UsageInformation> usageInformationInt( ) SIP_PYNAME( usageInformation );
389403

390-
static QHash<Qgis::RasterAttributeTableFieldUsage, QgsRasterAttributeTable::UsageInformation> sUsageInformation;
404+
static QHash<Qgis::RasterAttributeTableFieldUsage, QgsRasterAttributeTable::UsageInformation> sUsageInformation SIP_SKIP;
391405

392406
///@encond
393407

@@ -399,6 +413,9 @@ class CORE_EXPORT QgsRasterAttributeTable
399413
bool mIsDirty;
400414
QString mFilePath;
401415

416+
// Set type from fields.
417+
void setType( );
418+
402419
};
403420

404421
#endif // QGSRASTERATTRIBUTETABLE_H

‎src/core/raster/qgsrasterdataprovider.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -714,7 +714,7 @@ bool QgsRasterDataProvider::readFileBasedAttributeTable( int bandNumber, const Q
714714
}
715715
}
716716

717-
bool QgsRasterDataProvider::writeNativeAttributeTable( QString *errorMessage ) const //#spellok
717+
bool QgsRasterDataProvider::writeNativeAttributeTable( QString *errorMessage ) //#spellok
718718
{
719719
Q_UNUSED( errorMessage );
720720
return false;

‎src/core/raster/qgsrasterdataprovider.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -793,7 +793,7 @@ class CORE_EXPORT QgsRasterDataProvider : public QgsDataProvider, public QgsRast
793793
* \note No checks for Raster Attribute Table validity are performed when saving, it is client code responsibility to handle validation.
794794
* \since QGIS 3.30
795795
*/
796-
virtual bool writeNativeAttributeTable( QString *errorMessage SIP_OUT = nullptr ) const; //#spellok
796+
virtual bool writeNativeAttributeTable( QString *errorMessage SIP_OUT = nullptr ); //#spellok
797797

798798
/**
799799
* Reads the native attribute table, optionally reporting any error in \a errorMessage, returns TRUE on success.

‎src/core/raster/qgsrasterrendererregistry.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,11 +149,17 @@ QgsRasterRenderer *QgsRasterRendererRegistry::defaultRendererForDrawingStyle( Qg
149149
const int grayBand = 1;
150150

151151
// If the raster band has an attribute table try to use it.
152-
if ( QgsRasterAttributeTable *rat = provider->attributeTable( grayBand ) )
152+
QString ratErrorMessage;
153+
if ( QgsRasterAttributeTable *rat = provider->attributeTable( grayBand ); rat && rat->isValid( &ratErrorMessage ) )
153154
{
154155
renderer = rat->createRenderer( provider, grayBand );
155156
}
156157

158+
if ( ! ratErrorMessage.isEmpty() )
159+
{
160+
QgsDebugMsgLevel( QStringLiteral( "Invalid RAT from band 1, RAT was not used to create the renderer: %1." ).arg( ratErrorMessage ), 2 );
161+
}
162+
157163
if ( ! renderer )
158164
{
159165
renderer = new QgsSingleBandGrayRenderer( provider, grayBand );

‎src/gui/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ set(QGIS_GUI_SRCS
33
raster/qgsrasterattributetablewidget.cpp
44
raster/qgsrasterattributetabledialog.cpp
55
raster/qgsrasterattributetableaddcolumndialog.cpp
6+
raster/qgsrasterattributetableaddrowdialog.cpp
67
raster/qgscolorrampshaderwidget.cpp
78
raster/qgsmultibandcolorrendererwidget.cpp
89
raster/qgspalettedrendererwidget.cpp
@@ -1287,6 +1288,7 @@ set(QGIS_GUI_HDRS
12871288
raster/qgscreaterasterattributetabledialog.h
12881289
raster/qgsrasterattributetabledialog.h
12891290
raster/qgsrasterattributetableaddcolumndialog.h
1291+
raster/qgsrasterattributetableaddrowdialog.h
12901292
raster/qgscolorrampshaderwidget.h
12911293
raster/qgshillshaderendererwidget.h
12921294
raster/qgsmultibandcolorrendererwidget.h
@@ -1451,6 +1453,7 @@ set(QGIS_GUI_UI_HDRS
14511453
${CMAKE_CURRENT_BINARY_DIR}/../ui/ui_qgsqueryresultwidgetbase.h
14521454
${CMAKE_CURRENT_BINARY_DIR}/../ui/ui_qgsrasterattributetablewidgetbase.h
14531455
${CMAKE_CURRENT_BINARY_DIR}/../ui/ui_qgsrasterattributetabledialogbase.h
1456+
${CMAKE_CURRENT_BINARY_DIR}/../ui/ui_qgsrasterattributetableaddrowdialogbase.h
14541457
${CMAKE_CURRENT_BINARY_DIR}/../ui/ui_qgscreaterasterattributetabledialogbase.h
14551458
${CMAKE_CURRENT_BINARY_DIR}/../ui/ui_qgsrasterattributetableaddcolumndialog.h
14561459
${CMAKE_CURRENT_BINARY_DIR}/../ui/ui_qgssqlcomposerdialogbase.h

‎src/gui/raster/qgscreaterasterattributetabledialog.cpp

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -25,24 +25,13 @@ QgsCreateRasterAttributeTableDialog::QgsCreateRasterAttributeTableDialog( QgsRas
2525

2626
setupUi( this );
2727

28-
const bool nativeRatSupported { mRasterLayer->dataProvider()->providerCapabilities().testFlag( QgsRasterDataProvider::ProviderCapability::NativeRasterAttributeTable ) };
28+
// Apparently, some drivers (HFA) ignore Min/Max fields and set them to generic,
29+
// for this reason we disable the native support for thematic RATs (later in the loop)
30+
bool nativeRatSupported { mRasterLayer->dataProvider()->providerCapabilities().testFlag( QgsRasterDataProvider::ProviderCapability::NativeRasterAttributeTable ) };
2931

3032
connect( mNativeRadioButton, &QRadioButton::toggled, this, &QgsCreateRasterAttributeTableDialog::updateButtons );
3133
connect( mDbfRadioButton, &QRadioButton::toggled, this, &QgsCreateRasterAttributeTableDialog::updateButtons );
3234

33-
if ( ! nativeRatSupported )
34-
{
35-
mNativeRadioButton->setEnabled( false );
36-
mDbfRadioButton->setChecked( true );
37-
}
38-
else
39-
{
40-
mDbfPathWidget->setFilter( QStringLiteral( "VAT DBF Files (*.vat.dbf)" ) );
41-
if ( QFile::exists( mRasterLayer->dataProvider()->dataSourceUri( ) ) )
42-
{
43-
mDbfPathWidget->setFilePath( mRasterLayer->dataProvider()->dataSourceUri( ) + ".vat.dbf" );
44-
}
45-
}
4635

4736
// Check for existing rats
4837
QStringList existingRatsInfo;
@@ -52,6 +41,12 @@ QgsCreateRasterAttributeTableDialog::QgsCreateRasterAttributeTableDialog( QgsRas
5241
{
5342
if ( QgsRasterAttributeTable *rat = mRasterLayer->attributeTable( bandNo ) )
5443
{
44+
// disable the native support for thematic RATs
45+
if ( nativeRatSupported && rat->type() != Qgis::RasterAttributeTableType::Athematic )
46+
{
47+
nativeRatSupported = false;
48+
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." ) );
49+
}
5550
if ( ! rat->filePath().isEmpty() )
5651
{
5752
existingRatsInfo.push_back( tr( "Raster band %1 already has an associated attribute table at %2." ).arg( QString::number( bandNo ), rat->filePath() ) );
@@ -67,17 +62,39 @@ QgsCreateRasterAttributeTableDialog::QgsCreateRasterAttributeTableDialog( QgsRas
6762
if ( ! existingRatsInfo.isEmpty() )
6863
{
6964
mCreateInfoLabel->setText( mCreateInfoLabel->text().append( QStringLiteral( "<br><ul><li>" ) + existingRatsInfo.join( QStringLiteral( "</li><li>" ) ) ).append( QStringLiteral( "</ul>" ) ) );
65+
mCreateInfoLabel->show();
66+
}
67+
68+
if ( ! nativeRatSupported )
69+
{
70+
mNativeRadioButton->setEnabled( false );
71+
mDbfRadioButton->setChecked( true );
72+
}
73+
else
74+
{
75+
mDbfPathWidget->setFilter( QStringLiteral( "VAT DBF Files (*.vat.dbf)" ) );
76+
if ( QFile::exists( mRasterLayer->dataProvider()->dataSourceUri( ) ) )
77+
{
78+
mDbfPathWidget->setFilePath( mRasterLayer->dataProvider()->dataSourceUri( ) + ".vat.dbf" );
79+
}
7080
}
7181

7282
connect( mButtonBox, &QDialogButtonBox::accepted, this, &QDialog::accept );
7383
connect( mButtonBox, &QDialogButtonBox::rejected, this, &QDialog::reject );
84+
85+
updateButtons();
7486
}
7587

7688
QString QgsCreateRasterAttributeTableDialog::filePath() const
7789
{
7890
return mDbfPathWidget->filePath();
7991
}
8092

93+
bool QgsCreateRasterAttributeTableDialog::saveToFile() const
94+
{
95+
return mDbfRadioButton->isChecked();
96+
}
97+
8198
bool QgsCreateRasterAttributeTableDialog::openWhenDone() const
8299
{
83100
return mOpenRat->isChecked();

‎src/gui/raster/qgscreaterasterattributetabledialog.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ class GUI_EXPORT QgsCreateRasterAttributeTableDialog : public QDialog, private U
4848
*/
4949
QString filePath( ) const;
5050

51+
/**
52+
* Returns TRUE if the option to save to a file is selected.
53+
*/
54+
bool saveToFile( ) const;
55+
5156
/**
5257
* Returns TRUE if the option to open the newly created attribute table is checked.
5358
*/

‎src/gui/raster/qgsrasterattributetableaddcolumndialog.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,20 @@
2323

2424
class QgsRasterAttributeTable;
2525

26+
/**
27+
* The QgsRasterAttributeTableAddColumnDialog class collects options to add a new column to a raster attribute table.
28+
* \since QGIS 3.30
29+
*/
2630
class GUI_EXPORT QgsRasterAttributeTableAddColumnDialog : public QDialog, private Ui::QgsRasterAttributeTableAddColumnDialogBase
2731
{
2832
Q_OBJECT
2933
public:
3034

35+
/**
36+
* Creates a new QgsRasterAttributeTableAddColumnDialog
37+
* \param attributeTable the raster attribute table
38+
* \param parent optional parent
39+
*/
3140
QgsRasterAttributeTableAddColumnDialog( QgsRasterAttributeTable *attributeTable, QWidget *parent SIP_TRANSFERTHIS = nullptr );
3241

3342
/**
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/***************************************************************************
2+
qgsrasterattributetableaddrowdialog.cpp - QgsRasterAttributeTableAddRowDialog
3+
4+
---------------------
5+
begin : 18.10.2022
6+
copyright : (C) 2022 by ale
7+
email : [your-email-here]
8+
***************************************************************************
9+
* *
10+
* This program is free software; you can redistribute it and/or modify *
11+
* it under the terms of the GNU General Public License as published by *
12+
* the Free Software Foundation; either version 2 of the License, or *
13+
* (at your option) any later version. *
14+
* *
15+
***************************************************************************/
16+
#include "qgsrasterattributetableaddrowdialog.h"
17+
#include "qgsgui.h"
18+
19+
QgsRasterAttributeTableAddRowDialog::QgsRasterAttributeTableAddRowDialog( QWidget *parent )
20+
: QDialog( parent )
21+
{
22+
setupUi( this );
23+
QgsGui::enableAutoGeometryRestore( this );
24+
25+
}
26+
27+
bool QgsRasterAttributeTableAddRowDialog::insertAfter() const
28+
{
29+
return mAfter->isChecked();
30+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/***************************************************************************
2+
qgsrasterattributetableaddrowdialog.h - QgsRasterAttributeTableAddRowDialog
3+
4+
---------------------
5+
begin : 18.10.2022
6+
copyright : (C) 2022 by Alessandro Pasotti
7+
email : elpaso at itopen dot it
8+
***************************************************************************
9+
* *
10+
* This program is free software; you can redistribute it and/or modify *
11+
* it under the terms of the GNU General Public License as published by *
12+
* the Free Software Foundation; either version 2 of the License, or *
13+
* (at your option) any later version. *
14+
* *
15+
***************************************************************************/
16+
#ifndef QGSRASTERATTRIBUTETABLEADDROWDIALOG_H
17+
#define QGSRASTERATTRIBUTETABLEADDROWDIALOG_H
18+
19+
#include <QDialog>
20+
#include "qgis_gui.h"
21+
#include "qgis.h"
22+
#include "ui_qgsrasterattributetableaddrowdialogbase.h"
23+
24+
/**
25+
* The QgsRasterAttributeTableAddColumnDialog class collects options to add a new row to a raster attribute table.
26+
* \since QGIS 3.30
27+
*/
28+
class GUI_EXPORT QgsRasterAttributeTableAddRowDialog : public QDialog, private Ui::QgsRasterAttributeTableAddRowDialogBase
29+
{
30+
Q_OBJECT
31+
public:
32+
33+
/**
34+
* Creates a new QgsRasterAttributeTableAddRowDialog
35+
* \param attributeTable the raster attribute table
36+
* \param parent optional parent
37+
*/
38+
QgsRasterAttributeTableAddRowDialog( QWidget *parent SIP_TRANSFERTHIS = nullptr );
39+
40+
/**
41+
* Returns TRUE if the desired insertion position for the new row is after the currently selected row, FALSE if the insertion point is before.
42+
*/
43+
bool insertAfter() const;
44+
45+
46+
};
47+
48+
#endif // QGSRASTERATTRIBUTETABLEADDROWDIALOG_H

‎src/gui/raster/qgsrasterattributetablemodel.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
***************************************************************************/
1616
#include "qgsrasterattributetablemodel.h"
1717
#include <QColor>
18+
#include <QFont>
1819

1920

2021
QString QgsRasterAttributeTableModel::RAT_COLOR_HEADER_NAME = QObject::tr( "Color" );
@@ -392,6 +393,12 @@ QVariant QgsRasterAttributeTableModel::data( const QModelIndex &index, int role
392393
{
393394
return mRat->data().at( index.row() ).at( index.column() );
394395
}
396+
else if ( role == Qt::ItemDataRole::FontRole && ( field.isColor() || field.isRamp() ) )
397+
{
398+
QFont font;
399+
font.setItalic( true );
400+
return font;
401+
}
395402
}
396403
return QVariant();
397404
}

‎src/gui/raster/qgsrasterattributetablewidget.cpp

Lines changed: 81 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include "qgsapplication.h"
2020
#include "qgsmessagebar.h"
2121
#include "qgsrasterattributetableaddcolumndialog.h"
22+
#include "qgsrasterattributetableaddrowdialog.h"
2223
#include "qgscolorbutton.h"
2324
#include "qgsgradientcolorrampdialog.h"
2425
#include "qgspalettedrasterrenderer.h"
@@ -99,7 +100,6 @@ void QgsRasterAttributeTableWidget::init( int bandNumber )
99100
mAttributeTable = nullptr;
100101
mCurrentBand = 0;
101102
mRasterBandsComboBox->clear();
102-
mClassifyComboBox->clear();
103103

104104
QList<int> availableRats;
105105

@@ -141,42 +141,18 @@ void QgsRasterAttributeTableWidget::init( int bandNumber )
141141
updateButtons();
142142
} );
143143

144-
static_cast<QSortFilterProxyModel *>( mRATView->model() )->setSourceModel( mModel.get() );
145-
146-
const QList<QgsRasterAttributeTable::Field> tableFields { mAttributeTable->fields() };
147-
int fieldIdx { 0 };
148-
const QHash<Qgis::RasterAttributeTableFieldUsage, QgsRasterAttributeTable::UsageInformation> usageInfo { QgsRasterAttributeTable::usageInformation() };
149-
for ( const QgsRasterAttributeTable::Field &f : std::as_const( tableFields ) )
144+
connect( mModel.get(), &QgsRasterAttributeTableModel::columnsInserted, this, [ = ]( const QModelIndex &, int, int )
150145
{
151-
if ( usageInfo[f.usage].maybeClass )
152-
{
153-
mClassifyComboBox->addItem( QgsFields::iconForFieldType( f.type ), f.name, QVariant( fieldIdx ) );
154-
}
155-
fieldIdx++;
156-
}
146+
setDelegates();
147+
} );
157148

158-
if ( mAttributeTable->hasColor() )
159-
{
160-
if ( mAttributeTable->usages().contains( Qgis::RasterAttributeTableFieldUsage::Alpha ) )
161-
{
162-
mRATView->setItemDelegateForColumn( mAttributeTable->fields().count( ), new ColorAlphaDelegate( mRATView ) );
163-
}
164-
else
165-
{
166-
mRATView->setItemDelegateForColumn( mAttributeTable->fields().count( ), new ColorDelegate( mRATView ) );
167-
}
168-
}
169-
else if ( mAttributeTable->hasRamp() )
149+
connect( mModel.get(), &QgsRasterAttributeTableModel::columnsRemoved, this, [ = ]( const QModelIndex &, int, int )
170150
{
171-
if ( mAttributeTable->usages().contains( Qgis::RasterAttributeTableFieldUsage::AlphaMin ) )
172-
{
173-
mRATView->setItemDelegateForColumn( mAttributeTable->fields().count( ), new ColorRampAlphaDelegate( mRATView ) );
174-
}
175-
else
176-
{
177-
mRATView->setItemDelegateForColumn( mAttributeTable->fields().count( ), new ColorRampDelegate( mRATView ) );
178-
}
179-
}
151+
setDelegates();
152+
} );
153+
154+
static_cast<QSortFilterProxyModel *>( mRATView->model() )->setSourceModel( mModel.get() );
155+
setDelegates();
180156
}
181157
else
182158
{
@@ -250,7 +226,7 @@ void QgsRasterAttributeTableWidget::saveChanges()
250226
return;
251227
}
252228
}
253-
else if ( nativeRatSupported )
229+
else if ( newPath.isEmpty() )
254230
{
255231
saveToNative = true;
256232
}
@@ -371,8 +347,29 @@ void QgsRasterAttributeTableWidget::addRow()
371347
{
372348
if ( mAttributeTable )
373349
{
350+
// Default to append
351+
int position { mModel->rowCount( QModelIndex() ) };
352+
const QModelIndex currentIndex { mProxyModel->mapToSource( mRATView->selectionModel()->currentIndex() ) };
353+
354+
// If there is a selected row, ask if before of after.
355+
if ( currentIndex.isValid() )
356+
{
357+
// Ask the user where to insert the new row (before or after the currently
358+
// selected row).
359+
QgsRasterAttributeTableAddRowDialog dlg;
360+
if ( dlg.exec() != QDialog::DialogCode::Accepted )
361+
{
362+
return;
363+
}
364+
else
365+
{
366+
position = currentIndex.row() + ( dlg.insertAfter() ? 1 : 0 );
367+
}
368+
}
369+
374370
bool result { true };
375371
QString errorMessage;
372+
376373
QVariantList rowData;
377374

378375
QList<QgsRasterAttributeTable::Field> fields { mAttributeTable->fields() };
@@ -381,18 +378,15 @@ void QgsRasterAttributeTableWidget::addRow()
381378
rowData.push_back( QVariant( field.type ) );
382379
}
383380

384-
const QModelIndex currentIndex { mProxyModel->mapToSource( mRATView->selectionModel()->currentIndex() ) };
385-
if ( currentIndex.isValid() )
381+
result = mModel->insertRow( position, rowData, &errorMessage );
382+
383+
if ( ! result )
386384
{
387-
result = mModel->insertRow( currentIndex.row(), rowData, &errorMessage );
385+
notify( tr( "Error adding row" ), errorMessage, Qgis::MessageLevel::Critical );
388386
}
389387
else
390388
{
391-
result = mModel->insertRow( mModel->rowCount( QModelIndex() ), rowData, &errorMessage );
392-
}
393-
if ( ! result )
394-
{
395-
notify( tr( "Error adding row" ), errorMessage, Qgis::MessageLevel::Critical );
389+
mRATView->scrollTo( mRATView->model()->index( position, 0 ) );
396390
}
397391
}
398392
}
@@ -452,6 +446,50 @@ void QgsRasterAttributeTableWidget::notify( const QString &title, const QString
452446
}
453447
}
454448

449+
void QgsRasterAttributeTableWidget::setDelegates()
450+
{
451+
mClassifyComboBox->clear();
452+
if ( mAttributeTable )
453+
{
454+
const QList<QgsRasterAttributeTable::Field> tableFields { mAttributeTable->fields() };
455+
int fieldIdx { 0 };
456+
const QHash<Qgis::RasterAttributeTableFieldUsage, QgsRasterAttributeTable::UsageInformation> usageInfo { QgsRasterAttributeTable::usageInformation() };
457+
for ( const QgsRasterAttributeTable::Field &f : std::as_const( tableFields ) )
458+
{
459+
// Clear all delegates.
460+
mRATView->setItemDelegateForColumn( fieldIdx, nullptr );
461+
if ( usageInfo[f.usage].maybeClass )
462+
{
463+
mClassifyComboBox->addItem( QgsFields::iconForFieldType( f.type ), f.name, QVariant( fieldIdx ) );
464+
}
465+
fieldIdx++;
466+
}
467+
468+
if ( mAttributeTable->hasColor() )
469+
{
470+
if ( mAttributeTable->usages().contains( Qgis::RasterAttributeTableFieldUsage::Alpha ) )
471+
{
472+
mRATView->setItemDelegateForColumn( mAttributeTable->fields().count( ), new ColorAlphaDelegate( mRATView ) );
473+
}
474+
else
475+
{
476+
mRATView->setItemDelegateForColumn( mAttributeTable->fields().count( ), new ColorDelegate( mRATView ) );
477+
}
478+
}
479+
else if ( mAttributeTable->hasRamp() )
480+
{
481+
if ( mAttributeTable->usages().contains( Qgis::RasterAttributeTableFieldUsage::AlphaMin ) )
482+
{
483+
mRATView->setItemDelegateForColumn( mAttributeTable->fields().count( ), new ColorRampAlphaDelegate( mRATView ) );
484+
}
485+
else
486+
{
487+
mRATView->setItemDelegateForColumn( mAttributeTable->fields().count( ), new ColorRampDelegate( mRATView ) );
488+
}
489+
}
490+
}
491+
}
492+
455493

456494
///@cond private
457495

‎src/gui/raster/qgsrasterattributetablewidget.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ class GUI_EXPORT QgsRasterAttributeTableWidget : public QWidget, private Ui::Qgs
164164
void removeRow();
165165
void bandChanged( const int index );
166166
void notify( const QString &title, const QString &message, Qgis::MessageLevel level = Qgis::MessageLevel::Info );
167+
void setDelegates( );
167168

168169
};
169170

‎src/ui/raster/qgscreaterasterattributetabledialogbase.ui

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
<rect>
77
<x>0</x>
88
<y>0</y>
9-
<width>336</width>
10-
<height>206</height>
9+
<width>391</width>
10+
<height>222</height>
1111
</rect>
1212
</property>
1313
<property name="windowTitle">
@@ -17,7 +17,7 @@
1717
<item>
1818
<widget class="QLabel" name="mCreateInfoLabel">
1919
<property name="text">
20-
<string>&lt;p&gt;Create a new Raster Attribute Table (RAT) using the current symbology.&lt;/p&gt;</string>
20+
<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>
2121
</property>
2222
<property name="wordWrap">
2323
<bool>true</bool>
@@ -27,23 +27,23 @@
2727
<item>
2828
<widget class="QGroupBox" name="groupBox">
2929
<property name="title">
30-
<string>Raster Attribute Table Destination</string>
30+
<string>Raster Attribute Table Storage</string>
3131
</property>
32-
<layout class="QVBoxLayout" name="verticalLayout_2">
33-
<item>
32+
<layout class="QGridLayout" name="gridLayout">
33+
<item row="0" column="0">
3434
<widget class="QRadioButton" name="mNativeRadioButton">
3535
<property name="toolTip">
3636
<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>
3737
</property>
3838
<property name="text">
39-
<string>Data provider</string>
39+
<string>Managed by the data provider</string>
4040
</property>
4141
<property name="checked">
4242
<bool>true</bool>
4343
</property>
4444
</widget>
4545
</item>
46-
<item>
46+
<item row="1" column="0">
4747
<widget class="QRadioButton" name="mDbfRadioButton">
4848
<property name="toolTip">
4949
<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 @@
5353
</property>
5454
</widget>
5555
</item>
56-
<item>
57-
<widget class="QgsFileWidget" name="mDbfPathWidget" native="true"/>
56+
<item row="2" column="0">
57+
<widget class="QgsFileWidget" name="mDbfPathWidget" native="true">
58+
<property name="minimumSize">
59+
<size>
60+
<width>180</width>
61+
<height>24</height>
62+
</size>
63+
</property>
64+
</widget>
5865
</item>
5966
</layout>
6067
</widget>

‎tests/src/python/test_qgsrasterattributetable.py

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -730,9 +730,9 @@ def testRamp(self):
730730
rat.appendField(QgsRasterAttributeTable.Field('BlueMax', Qgis.RasterAttributeTableFieldUsage.BlueMax, QVariant.Int))
731731

732732
data_rows = [
733-
[2.599999, 2.99999, 'two.5-three', 0, 255, 0, 255, 0, 0],
734-
[0, 1.99999, 'zero-two', 255, 0, 0, 0, 255, 0],
735-
[2, 2.5, 'two-two.5', 0, 255, 0, 0, 0, 255],
733+
[2.50000000001, 3, 'two.5-three', 0, 255, 0, 255, 0, 0],
734+
[0, 2, 'zero-two', 255, 0, 0, 0, 255, 0],
735+
[2.00000000001, 2.5, 'two-two.5', 0, 255, 0, 0, 0, 255],
736736
]
737737

738738
for row in data_rows:
@@ -752,7 +752,7 @@ def testRamp(self):
752752
shader = renderer.shader()
753753

754754
func = shader.rasterShaderFunction()
755-
self.assertEqual([(int(100 * st.offset), st.color.name()) for st in func.sourceColorRamp().stops()], [(66, '#00ff00'), (66, '#00ff00'), (85, '#0000ff'), (85, '#00ff00')]
755+
self.assertEqual([(int(100 * st.offset), st.color.name()) for st in func.sourceColorRamp().stops()], [(66, '#00ff00'), (83, '#0000ff')]
756756
)
757757

758758
# Test range classes and discrete colors on float
@@ -777,6 +777,15 @@ def testRamp(self):
777777
rat.appendRow(row)
778778

779779
self.assertTrue(rat.isValid()[0])
780+
781+
# Test ordered
782+
ordered = rat.orderedRows()
783+
self.assertEqual(ordered, [
784+
[-1e+25, 3000000000000, 'red class', 'class 0', 255, 0, 0],
785+
[3000000000000, 1e+20, 'blue class', 'class 1', 0, 0, 255],
786+
[1e+20, 5e+25, 'green class', 'class 2', 0, 255, 0],
787+
])
788+
780789
raster.dataProvider().setAttributeTable(1, rat)
781790
ok, errors = raster.dataProvider().writeNativeAttributeTable() # spellok
782791
self.assertTrue(ok)
@@ -788,6 +797,23 @@ def testRamp(self):
788797
func = shader.rasterShaderFunction()
789798
self.assertEqual([i[0] for i in renderer.legendSymbologyItems()], ['red class', 'blue class', 'green class'])
790799

800+
# Test color less athematic RAT
801+
rat = raster.attributeTable(1)
802+
self.assertTrue(rat.removeField('Red')[0])
803+
self.assertTrue(rat.removeField('Green')[0])
804+
self.assertTrue(rat.removeField('Blue')[0])
805+
self.assertFalse(rat.hasColor())
806+
self.assertFalse(rat.hasRamp())
807+
808+
ramp = rat.colorRamp()
809+
ramp, labels = rat.colorRamp()
810+
self.assertEqual(len(ramp.stops()) + 1, len(labels))
811+
self.assertEqual(labels, ['-1e+25 - 3e+12', '3e+12 - 1e+20', '1e+20 - 5e+25'])
812+
813+
ramp, labels = rat.colorRamp(2)
814+
self.assertEqual(len(ramp.stops()) + 1, len(labels))
815+
self.assertEqual(labels, ['red class', 'blue class', 'green class'])
816+
791817

792818
if __name__ == '__main__':
793819
unittest.main()

0 commit comments

Comments
 (0)
Please sign in to comment.