QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
qgsrasterattributetable.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsrasterattributetable.cpp - QgsRasterAttributeTable
3
4 ---------------------
5 begin : 3.12.2021
6 copyright : (C) 2021 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
18#include "qgsvectorfilewriter.h"
19#include "qgsogrprovider.h"
20#include "qgsfileutils.h"
21#include "qgsrasterlayer.h"
24#include "qgsrastershader.h"
26
27#include <QLocale>
28
29#include <mutex>
30#include <cmath>
31
33std::once_flag usageInformationLoaderFlag;
34QHash<Qgis::RasterAttributeTableFieldUsage, QgsRasterAttributeTable::UsageInformation> QgsRasterAttributeTable::sUsageInformation;
36
37
39{
40 return mType;
41}
42
43
45{
46 const QList<Qgis::RasterAttributeTableFieldUsage> fieldUsages { usages() };
47 return fieldUsages.contains( Qgis::RasterAttributeTableFieldUsage::Red ) && fieldUsages.contains( Qgis::RasterAttributeTableFieldUsage::Green ) && fieldUsages.contains( Qgis::RasterAttributeTableFieldUsage::Blue );
48}
49
50bool QgsRasterAttributeTable::setColor( const int row, const QColor &color )
51{
52 if ( ! hasColor() || row < 0 || row >= mData.count() )
53 {
54 return false;
55 }
56
57 for ( int idx = 0; idx < mFields.count(); ++idx )
58 {
59 const Field f { mFields.at( idx ) };
60 switch ( f.usage )
61 {
63 setValue( row, idx, color.red() );
64 break;
66 setValue( row, idx, color.green() );
67 break;
69 setValue( row, idx, color.blue() );
70 break;
72 setValue( row, idx, color.alpha() );
73 break;
74 default:
75 break;
76 }
77 }
78 return true;
79}
80
81
82bool QgsRasterAttributeTable::setRamp( const int row, const QColor &colorMin, const QColor &colorMax )
83{
84 if ( ! hasRamp() || row < 0 || row >= mData.count() )
85 {
86 return false;
87 }
88
89 int idx = 0;
90 for ( Field &f : mFields )
91 {
92 switch ( f.usage )
93 {
95 setValue( row, idx, colorMin.red() );
96 break;
98 setValue( row, idx, colorMin.green() );
99 break;
101 setValue( row, idx, colorMin.blue() );
102 break;
104 setValue( row, idx, colorMin.alpha() );
105 break;
107 setValue( row, idx, colorMax.red() );
108 break;
110 setValue( row, idx, colorMax.green() );
111 break;
113 setValue( row, idx, colorMax.blue() );
114 break;
116 setValue( row, idx, colorMax.alpha() );
117 break;
118 default:
119 break;
120 }
121 idx++;
122 }
123 return true;
124}
125
127{
128 const QList<Qgis::RasterAttributeTableFieldUsage> fieldUsages { usages() };
130}
131
132
133QList<Qgis::RasterAttributeTableFieldUsage> QgsRasterAttributeTable::usages() const
134{
135 QList<Qgis::RasterAttributeTableFieldUsage> usages;
136 for ( const QgsRasterAttributeTable::Field &field : std::as_const( mFields ) )
137 {
138 usages.push_back( field.usage );
139 }
140 return usages;
141}
142
144QList<int> QgsRasterAttributeTable::intUsages( ) const
145{
146 QList<int> usages;
147 for ( const QgsRasterAttributeTable::Field &field : std::as_const( mFields ) )
148 {
149 usages.push_back( static_cast<int>( field.usage ) );
150 }
151 return usages;
152}
154
155QColor QgsRasterAttributeTable::color( int row ) const
156{
157 QList<Qgis::RasterAttributeTableFieldUsage> fieldUsages { usages() };
158 // No ramps support here
159 if ( hasColor() && row < mData.count( )
160 && fieldUsages.contains( Qgis::RasterAttributeTableFieldUsage::Red )
161 && fieldUsages.contains( Qgis::RasterAttributeTableFieldUsage::Green )
162 && fieldUsages.contains( Qgis::RasterAttributeTableFieldUsage::Blue ) )
163 {
164 const QVariantList rowData = mData.at( row );
165 QColor color { rowData.at( fieldUsages.indexOf( Qgis::RasterAttributeTableFieldUsage::Red ) ).toInt(),
166 rowData.at( fieldUsages.indexOf( Qgis::RasterAttributeTableFieldUsage::Green ) ).toInt(),
167 rowData.at( fieldUsages.indexOf( Qgis::RasterAttributeTableFieldUsage::Blue ) ).toInt() };
168 if ( fieldUsages.contains( Qgis::RasterAttributeTableFieldUsage::Alpha ) )
169 {
170 color.setAlpha( rowData.at( fieldUsages.indexOf( Qgis::RasterAttributeTableFieldUsage::Alpha ) ).toInt() );
171 }
172 return color;
173 }
174 return QColor();
175}
176
178{
179 if ( ! hasRamp() || row < 0 || row >= mData.count() )
180 {
181 return QgsGradientColorRamp();
182 }
183 QList<Qgis::RasterAttributeTableFieldUsage> fieldUsages { usages() };
184 const QVariantList rowData = mData.at( row );
185 QColor colorMin { rowData.at( fieldUsages.indexOf( Qgis::RasterAttributeTableFieldUsage::RedMin ) ).toInt(),
186 rowData.at( fieldUsages.indexOf( Qgis::RasterAttributeTableFieldUsage::GreenMin ) ).toInt(),
187 rowData.at( fieldUsages.indexOf( Qgis::RasterAttributeTableFieldUsage::BlueMin ) ).toInt() };
188 if ( fieldUsages.contains( Qgis::RasterAttributeTableFieldUsage::AlphaMin ) )
189 {
190 colorMin.setAlpha( rowData.at( fieldUsages.indexOf( Qgis::RasterAttributeTableFieldUsage::AlphaMin ) ).toInt() );
191 }
192 QColor colorMax { rowData.at( fieldUsages.indexOf( Qgis::RasterAttributeTableFieldUsage::RedMax ) ).toInt(),
193 rowData.at( fieldUsages.indexOf( Qgis::RasterAttributeTableFieldUsage::GreenMax ) ).toInt(),
194 rowData.at( fieldUsages.indexOf( Qgis::RasterAttributeTableFieldUsage::BlueMax ) ).toInt() };
195 if ( fieldUsages.contains( Qgis::RasterAttributeTableFieldUsage::AlphaMax ) )
196 {
197 colorMax.setAlpha( rowData.at( fieldUsages.indexOf( Qgis::RasterAttributeTableFieldUsage::AlphaMax ) ).toInt() );
198 }
199 return QgsGradientColorRamp( colorMin, colorMax );
200}
201
202QList<QgsRasterAttributeTable::Field> QgsRasterAttributeTable::fields() const
203{
204 return mFields;
205}
206
208{
209 QgsFields qFields;
210
211 for ( const QgsRasterAttributeTable::Field &field : std::as_const( mFields ) )
212 {
213 qFields.append( QgsField( field.name, field.type ) );
214 }
215 return qFields;
216}
217
219{
220 QgsFeatureList features;
221 for ( const QVariantList &row : std::as_const( mData ) )
222 {
223 QgsAttributes attributes;
224 for ( const auto &cell : std::as_const( row ) )
225 {
226 attributes.append( cell );
227 }
228 QgsFeature feature { qgisFields() };
229 feature.setAttributes( attributes );
230 features.append( feature );
231 }
232 return features;
233}
234
236{
237 return mIsDirty;
238}
239
241{
242 mIsDirty = isDirty;
243}
244
245bool QgsRasterAttributeTable::insertField( int position, const Field &field, QString *errorMessage )
246{
247
248 const int realPos { std::clamp( position, 0, static_cast<int>( mFields.count() ) ) };
249
250 if ( field.name.isEmpty() )
251 {
252 if ( errorMessage )
253 {
254 *errorMessage = tr( "Field name must not be empty." );
255 }
256 return false;
257 }
258
259 // Check for duplicate names
260 bool ok;
261 fieldByName( field.name, &ok );
262
263 if ( ok )
264 {
265 if ( errorMessage )
266 {
267 *errorMessage = tr( "A field with name '%1' already exists." ).arg( field.name );
268 }
269 return false;
270 }
271
272 // Check for duplicate unique usages
273 static const QList<Qgis::RasterAttributeTableFieldUsage> uniqueUsages {{
291 }};
292
293 if ( uniqueUsages.contains( field.usage ) && ! fieldsByUsage( field.usage ).isEmpty() )
294 {
295 if ( errorMessage )
296 {
297 *errorMessage = tr( "A field with unique usage '%1' already exists." ).arg( usageName( field.usage ) );
298 }
299 return false;
300 }
301
302 mFields.insert( realPos, field );
303
304 for ( auto it = mData.begin(); it != mData.end(); ++it )
305 {
306 QVariant defaultValue( field.type );
307 // Set default values
308 switch ( field.type )
309 {
310 case QVariant::Type::Char:
311 case QVariant::Type::Int:
312 case QVariant::Type::UInt:
313 case QVariant::Type::LongLong:
314 case QVariant::Type::ULongLong:
315 case QVariant::Type::Double:
316 defaultValue = 0;
317 break;
318 default:
319 defaultValue = QString();
320 }
321 it->insert( realPos, defaultValue );
322 }
323
324 // Set/change the table type from the value field type
326 {
328 }
330 {
332 }
333
334 setType();
335 setDirty( true );
336
337 return true;
338}
339
340bool QgsRasterAttributeTable::insertField( int position, const QString &name, const Qgis::RasterAttributeTableFieldUsage usage, const QVariant::Type type, QString *errorMessage )
341{
342 return insertField( position, { name, usage, type}, errorMessage );
343}
344
345bool QgsRasterAttributeTable::insertColor( int position, QString *errorMessage )
346{
348 int idx { position };
349 for ( const Qgis::RasterAttributeTableFieldUsage usage : std::as_const( colors ) )
350 {
351 if ( ! insertField( idx, usageName( usage ), usage, QVariant::Type::Int, errorMessage ) )
352 {
353 return false;
354 }
355 ++idx;
356 }
357 return true;
358}
359
361{
362 if ( fieldIndex < 0 || fieldIndex >= fields().count( ) )
363 {
364 return false;
365 }
366
367 const Field field { fields().at( fieldIndex ) };
368 if ( ! usageInformation()[ usage ].allowedTypes.contains( field.type ) )
369 {
370 return false;
371 }
372
373 mFields[ fieldIndex ].usage = usage;
374 setType();
375
376 return true;
377}
378
379bool QgsRasterAttributeTable::insertRamp( int position, QString *errorMessage )
380{
382 {
383 if ( errorMessage )
384 {
385 *errorMessage = tr( "A color ramp can only be added to an athematic attribute table." );
386 }
387 }
389 int idx { position };
390 for ( const Qgis::RasterAttributeTableFieldUsage usage : std::as_const( colors ) )
391 {
392 if ( ! insertField( idx, usageName( usage ), usage, QVariant::Type::Int, errorMessage ) )
393 {
394 return false;
395 }
396 ++idx;
397 }
398 return true;
399}
400
401bool QgsRasterAttributeTable::appendField( const QString &name, const Qgis::RasterAttributeTableFieldUsage usage, const QVariant::Type type, QString *errorMessage )
402{
403 return insertField( mFields.count(), name, usage, type, errorMessage );
404}
405
406bool QgsRasterAttributeTable::appendField( const Field &field, QString *errorMessage )
407{
408 return insertField( mFields.count(), field, errorMessage );
409}
410
411bool QgsRasterAttributeTable::removeField( const QString &name, QString *errorMessage )
412{
413 const auto toRemove { std::find_if( mFields.begin(), mFields.end(), [ &name ]( Field & f ) -> bool {
414 return f.name == name;
415 } )};
416
417 if ( toRemove != mFields.end() )
418 {
419 const int idx { static_cast<int>( std::distance( mFields.begin(), toRemove ) ) };
420 mFields.erase( toRemove );
421 for ( auto it = mData.begin(); it != mData.end(); ++it )
422 {
423 it->removeAt( idx );
424 }
425 setType();
426 setDirty( true );
427 return true;
428 }
429
430 if ( errorMessage )
431 {
432 *errorMessage = tr( "A field with name '%1' was not found." ).arg( name );
433 }
434 return false;
435}
436
437bool QgsRasterAttributeTable::insertRow( int position, const QVariantList &rowData, QString *errorMessage )
438{
439
440 const int realPos { std::clamp( position, 0, static_cast<int>( mData.count() ) ) };
441
442 if ( rowData.size() != mFields.size() )
443 {
444 if ( errorMessage )
445 {
446 *errorMessage = tr( "Row element count differs from field count (%1)." ).arg( mFields.size() );
447 }
448 return false;
449 }
450
451 QVariantList dataValid;
452
453 for ( int idx = 0; idx < mFields.count(); ++idx )
454 {
455 QVariant cell( rowData[ idx ] );
456 if ( ! cell.canConvert( mFields.at( idx ).type ) || ! cell.convert( mFields.at( idx ).type ) )
457 {
458 if ( errorMessage )
459 {
460 *errorMessage = tr( "Row data at column %1 cannot be converted to field type (%2)." ).arg( idx ).arg( QVariant::typeToName( mFields.at( idx ).type ) );
461 }
462 return false;
463 }
464 else
465 {
466 dataValid.append( cell );
467 }
468 }
469
470 mData.insert( realPos, dataValid );
471 setDirty( true );
472 return true;
473}
474
475bool QgsRasterAttributeTable::removeRow( int position, QString *errorMessage )
476{
477 if ( position >= mData.count() || position < 0 || mData.isEmpty() )
478 {
479 if ( errorMessage )
480 {
481 *errorMessage = tr( "Position is not valid or the table is empty." );
482 }
483 return false;
484 }
485 mData.removeAt( position );
486 setDirty( true );
487 return true;
488}
489
490bool QgsRasterAttributeTable::appendRow( const QVariantList &data, QString *errorMessage )
491{
492 return insertRow( mData.count(), data, errorMessage );
493}
494
495bool QgsRasterAttributeTable::writeToFile( const QString &path, QString *errorMessage )
496{
498 options.actionOnExistingFile = QgsVectorFileWriter::ActionOnExistingFile::CreateOrOverwriteFile;
499 options.driverName = QStringLiteral( "ESRI Shapefile" );
500 options.fileEncoding = QStringLiteral( "UTF-8" );
501 options.layerOptions = QStringList() << QStringLiteral( "SHPT=NULL" );
502
503 std::unique_ptr<QgsVectorFileWriter> writer;
504
505 // Strip .dbf from path because OGR adds it back
506 QString cleanedPath { path };
507 if ( path.endsWith( QStringLiteral( ".dbf" ), Qt::CaseSensitivity::CaseInsensitive ) )
508 {
509 cleanedPath.chop( 4 );
510 }
511
512 cleanedPath = QgsFileUtils::ensureFileNameHasExtension( cleanedPath, {{ QStringLiteral( ".vat" ) } } );
513
515
516 cleanedPath.append( QStringLiteral( ".dbf" ) );
517
518 const QgsVectorFileWriter::WriterError error { writer->hasError() };
519 if ( error != QgsVectorFileWriter::WriterError::NoError )
520 {
521 if ( errorMessage )
522 {
523 *errorMessage = tr( "Error creating Raster Attribute Table table: %1." ).arg( writer->errorMessage() );
524 }
525 return false;
526 }
527
528 QgsFeatureList features { qgisFeatures() };
529 bool result { writer->addFeatures( features ) };
530
531 if ( ! result )
532 {
533 if ( errorMessage )
534 {
535 *errorMessage = tr( "Error creating Raster Attribute Table table: could not add rows." );
536 }
537 return false;
538 }
539
540 result = writer->flushBuffer();
541
542 if ( result )
543 {
544 mFilePath = cleanedPath;
545 setDirty( false );
546 }
547
548 return result;
549}
550
551bool QgsRasterAttributeTable::readFromFile( const QString &path, QString *errorMessage )
552{
553 QgsOgrProvider ratDbfSource { path, QgsDataProvider::ProviderOptions() };
554 if ( ! ratDbfSource.isValid() )
555 {
556 if ( errorMessage )
557 {
558 *errorMessage = tr( "Error reading Raster Attribute Table table from file: invalid layer." );
559 }
560 return false;
561 }
562
563 QList<Field> oldFields = mFields;
564 QList<QVariantList> oldData = mData;
565
566 mFields.clear();
567 mData.clear();
568
569 bool hasValueField { false };
570 for ( const QgsField &field : ratDbfSource.fields() )
571 {
572 const Qgis::RasterAttributeTableFieldUsage usage { guessFieldUsage( field.name(), field.type() ) };
573 QVariant::Type type { field.type() };
574 // DBF sets all int fields to long but for RGBA it doesn't make sense
575 if ( type == QVariant::Type::LongLong &&
580 {
581 type = QVariant::Int;
582 }
583
585 {
586 hasValueField = true;
587 }
588
589 QgsRasterAttributeTable::Field ratField { field.name(), usage, type };
590 if ( ! appendField( ratField, errorMessage ) )
591 {
592 mFields = oldFields;
593 mData = oldData;
594 return false;
595 }
596 }
597
598 // Do we have a value field? If not, try to guess one
599 if ( ! hasValueField && mFields.count() > 1 && ( mFields.at( 0 ).type == QVariant::Int || mFields.at( 0 ).type == QVariant::Char || mFields.at( 0 ).type == QVariant::UInt || mFields.at( 0 ).type == QVariant::LongLong || mFields.at( 0 ).type == QVariant::ULongLong ) )
600 {
602 }
603
604 const int fieldCount { static_cast<int>( ratDbfSource.fields().count( ) ) };
605 QgsFeature f;
606 QgsFeatureIterator fit { ratDbfSource.getFeatures( QgsFeatureRequest() ) };
607 while ( fit.nextFeature( f ) )
608 {
609 if ( f.attributeCount() != fieldCount )
610 {
611 if ( errorMessage )
612 {
613 *errorMessage = tr( "Error reading Raster Attribute Table table from file: number of fields and number of attributes do not match." );
614 }
615 mFields = oldFields;
616 mData = oldData;
617 return false;
618 }
619 appendRow( f.attributes().toList() );
620 }
621
622 mFilePath = path;
623 setDirty( false );
624
625 return true;
626}
627
628
629bool QgsRasterAttributeTable::isValid( QString *errorMessage ) const
630{
631 QStringList errors;
632
633 if ( mFields.isEmpty() )
634 {
635 errors.push_back( tr( "The attribute table has no fields." ) );
636 }
637
638 if ( mData.isEmpty() )
639 {
640 errors.push_back( tr( "The attribute table has no rows." ) );
641 }
642
643 const QList<Qgis::RasterAttributeTableFieldUsage> fieldUsages { usages() };
644 const bool isMinMax { fieldUsages.contains( Qgis::RasterAttributeTableFieldUsage::MinMax ) };
645 const bool isValueRamp { fieldUsages.contains( Qgis::RasterAttributeTableFieldUsage::Min ) &&fieldUsages.contains( Qgis::RasterAttributeTableFieldUsage::Max ) };
646 if ( ! isMinMax && ! isValueRamp )
647 {
648 errors.push_back( tr( "The attribute table has no MinMax nor a pair of Min and Max fields." ) );
649 }
650
651 // Check color
652 if ( fieldUsages.contains( Qgis::RasterAttributeTableFieldUsage::Red ) || fieldUsages.contains( Qgis::RasterAttributeTableFieldUsage::Green ) || fieldUsages.contains( Qgis::RasterAttributeTableFieldUsage::Blue ) )
653 {
654 if ( !( fieldUsages.contains( Qgis::RasterAttributeTableFieldUsage::Red ) && fieldUsages.contains( Qgis::RasterAttributeTableFieldUsage::Green ) && fieldUsages.contains( Qgis::RasterAttributeTableFieldUsage::Blue ) ) )
655 {
656 errors.push_back( tr( "The attribute table has some but not all the fields required for color definition (Red, Green, Blue)." ) );
657 }
658 }
659
660 // Check ramp
662 {
663 if ( !( fieldUsages.contains( Qgis::RasterAttributeTableFieldUsage::RedMin ) && fieldUsages.contains( Qgis::RasterAttributeTableFieldUsage::GreenMin ) && fieldUsages.contains( Qgis::RasterAttributeTableFieldUsage::BlueMin ) && fieldUsages.contains( Qgis::RasterAttributeTableFieldUsage::RedMax ) && fieldUsages.contains( Qgis::RasterAttributeTableFieldUsage::GreenMax ) && fieldUsages.contains( Qgis::RasterAttributeTableFieldUsage::BlueMax ) ) )
664 {
665 errors.push_back( tr( "The attribute table has some but not all the fields required for color ramp definition (RedMin, GreenMin, BlueMin, RedMax, GreenMax, BlueMax)." ) );
666 }
667 else if ( ! isValueRamp )
668 {
669 errors.push_back( tr( "The attribute table has all the fields required for color ramp definition (RedMin, GreenMin, BlueMin, RedMax, GreenMax, BlueMax) but no Min and Max field." ) );
670 }
671 }
672
673 if ( errorMessage && ! errors.isEmpty() )
674 {
675 *errorMessage = errors.join( QChar( '\n' ) );
676 }
677
678 return errors.isEmpty();
679}
680
681const QList<QList<QVariant> > QgsRasterAttributeTable::data() const
682{
683 return mData;
684}
685
687{
688 for ( const Field &f : std::as_const( mFields ) )
689 {
690 if ( f.name == name )
691 {
692 if ( ok )
693 {
694 *ok = true;
695 }
696 return f;
697 }
698 }
699 if ( ok )
700 {
701 *ok = false;
702 }
703 return Field( QString(), Qgis::RasterAttributeTableFieldUsage::Generic, QVariant::String );
704}
705
706const QList<QgsRasterAttributeTable::Field> QgsRasterAttributeTable::fieldsByUsage( const Qgis::RasterAttributeTableFieldUsage fieldUsage ) const
707{
708 QList<QgsRasterAttributeTable::Field> result;
709 for ( const Field &f : std::as_const( mFields ) )
710 {
711 if ( f.usage == fieldUsage )
712 {
713 result.push_back( f );
714 }
715 }
716 return result;
717}
718
719bool QgsRasterAttributeTable::setValue( const int row, const int column, const QVariant &value )
720{
721 if ( row < 0 || row >= mData.count( ) || column < 0 || column >= mData[ row ].count( ) )
722 {
723 return false;
724 }
725
726 QVariant newVal = value;
727 if ( column >= mFields.length() || ! value.canConvert( mFields.at( column ).type ) || ! newVal.convert( mFields.at( column ).type ) )
728 {
729 return false;
730 }
731
732 const QVariant oldVal = mData[ row ][ column ];
733
734 if ( newVal != oldVal )
735 {
736 mData[ row ][ column ] = newVal;
737 setDirty( true );
738 }
739
740 return true;
741}
742
743QVariant QgsRasterAttributeTable::value( const int row, const int column ) const
744{
745 if ( row < 0 || row >= mData.count( ) || column < 0 || column >= mData[ row ].count( ) )
746 {
747 return QVariant();
748 }
749 return mData[ row ][ column ];
750}
751
753{
754 const QList<Qgis::RasterAttributeTableFieldUsage> fieldUsages { usages() };
755 bool ok { false };
756 int fieldIdx { -1 };
757
758 if ( fieldUsages.contains( Qgis::RasterAttributeTableFieldUsage::MinMax ) )
759 {
760 fieldIdx = fieldUsages.indexOf( Qgis::RasterAttributeTableFieldUsage::MinMax );
761 }
762 else if ( fieldUsages.contains( Qgis::RasterAttributeTableFieldUsage::Min ) )
763 {
764 fieldIdx = fieldUsages.indexOf( Qgis::RasterAttributeTableFieldUsage::Min );
765 }
766
767 double min { std::numeric_limits<double>::max() };
768 for ( int rowIdx = 0; rowIdx < mData.count(); ++rowIdx )
769 {
770 min = std::min( min, value( rowIdx, fieldIdx ).toDouble( &ok ) );
771 if ( ! ok )
772 {
773 return std::numeric_limits<double>::quiet_NaN();
774 }
775 }
776
777 if ( fieldIdx == -1 || ! ok )
778 {
779 return std::numeric_limits<double>::quiet_NaN();
780 }
781 else
782 {
783 return min;
784 }
785}
786
788{
789 const QList<Qgis::RasterAttributeTableFieldUsage> fieldUsages { usages() };
790 bool ok { false };
791 int fieldIdx { -1 };
792
793 if ( fieldUsages.contains( Qgis::RasterAttributeTableFieldUsage::MinMax ) )
794 {
795 fieldIdx = fieldUsages.indexOf( Qgis::RasterAttributeTableFieldUsage::MinMax );
796 }
797 else if ( fieldUsages.contains( Qgis::RasterAttributeTableFieldUsage::Max ) )
798 {
799 fieldIdx = fieldUsages.indexOf( Qgis::RasterAttributeTableFieldUsage::Max );
800 }
801
802 double max { std::numeric_limits<double>::lowest() };
803 for ( int rowIdx = 0; rowIdx < mData.count(); ++rowIdx )
804 {
805 max = std::max( max, value( rowIdx, fieldIdx ).toDouble( &ok ) );
806 if ( ! ok )
807 {
808 return std::numeric_limits<double>::quiet_NaN();
809 }
810 }
811
812 if ( fieldIdx == -1 || ! ok )
813 {
814 return std::numeric_limits<double>::quiet_NaN();
815 }
816 else
817 {
818 return max;
819 }
820}
821
822QVariantList QgsRasterAttributeTable::row( const double matchValue ) const
823{
824 if ( ! isValid() )
825 {
826 return QVariantList();
827 }
828
829 const QList<Qgis::RasterAttributeTableFieldUsage> fieldUsages { usages() };
830
831 if ( fieldUsages.contains( Qgis::RasterAttributeTableFieldUsage::MinMax ) )
832 {
833 const int colIdx { static_cast<int>( fieldUsages.indexOf( Qgis::RasterAttributeTableFieldUsage::MinMax ) ) };
834 for ( int rowIdx = 0; rowIdx < mData.count(); ++rowIdx )
835 {
836 bool ok;
837 if ( matchValue == value( rowIdx, colIdx ).toDouble( &ok ) && ok )
838 {
839 return mData.at( rowIdx );
840 }
841 }
842 }
843 else
844 {
845 const int minColIdx { static_cast<int>( fieldUsages.indexOf( Qgis::RasterAttributeTableFieldUsage::Min ) ) };
846 const int maxColIdx { static_cast<int>( fieldUsages.indexOf( Qgis::RasterAttributeTableFieldUsage::Max ) ) };
847 for ( int rowIdx = 0; rowIdx < mData.count(); ++rowIdx )
848 {
849 bool ok;
850 if ( matchValue >= value( rowIdx, minColIdx ).toDouble( &ok ) && ok )
851 {
852 if ( matchValue < value( rowIdx, maxColIdx ).toDouble( &ok ) && ok )
853 {
854 return mData.at( rowIdx );
855 }
856 }
857 }
858 }
859 return QVariantList();
860}
861
863{
864 static const QStringList minValueNames { {
865 QStringLiteral( "min" ),
866 QStringLiteral( "min_value" ),
867 QStringLiteral( "min value" ),
868 QStringLiteral( "value min" ),
869 QStringLiteral( "value_min" ),
870 } };
871
872 static const QStringList maxValueNames { {
873 QStringLiteral( "max" ),
874 QStringLiteral( "max_value" ),
875 QStringLiteral( "max value" ),
876 QStringLiteral( "value max" ),
877 QStringLiteral( "value_max" ),
878 } };
879
880 const QString fieldLower { name.toLower() };
881
882 if ( type == QVariant::Double || type == QVariant::Int || type == QVariant::UInt || type == QVariant::LongLong || type == QVariant::ULongLong )
883 {
884 if ( minValueNames.contains( fieldLower ) )
885 {
887 }
888 else if ( maxValueNames.contains( fieldLower ) )
889 {
891 }
892 else if ( fieldLower == QLatin1String( "value" ) )
893 {
895 }
896 else if ( fieldLower == QLatin1String( "count" ) )
897 {
898 // This could really be max count but it's more likely pixel count
900 }
901 // Colors (not double)
902 else if ( type != QVariant::Double )
903 {
904 if ( fieldLower.contains( "red" ) || fieldLower == QLatin1String( "r" ) )
905 {
906 if ( fieldLower.contains( "min" ) )
907 {
909 }
910 else if ( fieldLower.contains( "max" ) )
911 {
913 }
914 else
915 {
917 }
918 }
919 else if ( fieldLower.contains( "green" ) || fieldLower == QLatin1String( "g" ) )
920 {
921 if ( fieldLower.contains( "min" ) )
922 {
924 }
925 else if ( fieldLower.contains( "max" ) )
926 {
928 }
929 else
930 {
932 }
933 }
934 else if ( fieldLower.contains( "blue" ) || fieldLower == QLatin1String( "b" ) )
935 {
936 if ( fieldLower.contains( "min" ) )
937 {
939 }
940 else if ( fieldLower.contains( "max" ) )
941 {
943 }
944 else
945 {
947 }
948 }
949 else if ( fieldLower.contains( "alpha" ) || fieldLower == QLatin1String( "a" ) )
950 {
951 if ( fieldLower.contains( "min" ) )
952 {
954 }
955 else if ( fieldLower.contains( "max" ) )
956 {
958 }
959 else
960 {
962 }
963 }
964 }
965 // end colors
966 }
967
968 if ( type == QVariant::String ) // default to name for strings
969 {
971 }
972
973 // default to generic for all other cases
975
976}
977
979{
980 switch ( usage )
981 {
983 return tr( "Red" );
985 return tr( "Green" );
987 return tr( "Blue" );
989 return tr( "Alpha" );
991 return tr( "Red Minimum" );
993 return tr( "Green Minimum" );
995 return tr( "Blue Minimum" );
997 return tr( "Alpha Minimum" );
999 return tr( "Red Maximum" );
1001 return tr( "Green Maximum" );
1003 return tr( "Blue Maximum" );
1005 return tr( "Alpha Maximum" );
1007 return tr( "Generic" );
1009 return tr( "Name" );
1011 return tr( "Pixel Count" );
1013 return tr( "Maximum Count" );
1015 return tr( "Value" );
1017 return tr( "Minimum Value" );
1019 return tr( "Maximum Value" );
1020 }
1021 return QString();
1022}
1023
1024QList<Qgis::RasterAttributeTableFieldUsage> QgsRasterAttributeTable::valueAndColorFieldUsages()
1025{
1026 static const QList<Qgis::RasterAttributeTableFieldUsage> valueColorUsages {{
1042 }};
1043 return valueColorUsages;
1044}
1045
1047{
1048
1049 if ( ! raster || ! raster->dataProvider() || ! raster->isValid() )
1050 {
1051 return nullptr;
1052 }
1053
1054 const QgsRasterRenderer *renderer = raster->renderer();
1055
1056 if ( ! renderer )
1057 {
1058 return nullptr;
1059 }
1060
1061 if ( const QgsPalettedRasterRenderer *palettedRenderer = dynamic_cast<const QgsPalettedRasterRenderer *>( renderer ) )
1062 {
1064 rat->appendField( QStringLiteral( "Value" ), Qgis::RasterAttributeTableFieldUsage::MinMax, QVariant::Type::Double );
1065 rat->appendField( QStringLiteral( "Class" ), Qgis::RasterAttributeTableFieldUsage::Name, QVariant::Type::String );
1066 rat->appendField( QStringLiteral( "Red" ), Qgis::RasterAttributeTableFieldUsage::Red, QVariant::Type::Int );
1067 rat->appendField( QStringLiteral( "Green" ), Qgis::RasterAttributeTableFieldUsage::Green, QVariant::Type::Int );
1068 rat->appendField( QStringLiteral( "Blue" ), Qgis::RasterAttributeTableFieldUsage::Blue, QVariant::Type::Int );
1069 rat->appendField( QStringLiteral( "Alpha" ), Qgis::RasterAttributeTableFieldUsage::Alpha, QVariant::Type::Int );
1070
1071 const QgsPalettedRasterRenderer::ClassData classes { palettedRenderer->classes() };
1072
1073 for ( const QgsPalettedRasterRenderer::Class &klass : std::as_const( classes ) )
1074 {
1075 rat->appendRow( QVariantList() << klass.value << klass.label << 0 << 0 << 0 << 255 );
1076 rat->setColor( rat->data().length() - 1, klass.color );
1077 }
1078
1079 if ( bandNumber )
1080 {
1081 *bandNumber = palettedRenderer->inputBand();
1082 }
1083 return rat;
1084 }
1085 else if ( const QgsSingleBandPseudoColorRenderer *pseudoColorRenderer = dynamic_cast<const QgsSingleBandPseudoColorRenderer *>( renderer ) )
1086 {
1087 if ( const QgsRasterShader *shader = pseudoColorRenderer->shader() )
1088 {
1089 if ( const QgsColorRampShader *shaderFunction = dynamic_cast<const QgsColorRampShader *>( shader->rasterShaderFunction() ) )
1090 {
1092 switch ( shaderFunction->colorRampType() )
1093 {
1094
1095 case QgsColorRampShader::Type::Interpolated:
1096 {
1097 rat->appendField( QStringLiteral( "Min" ), Qgis::RasterAttributeTableFieldUsage::Min, QVariant::Type::Double );
1098 rat->appendField( QStringLiteral( "Max" ), Qgis::RasterAttributeTableFieldUsage::Max, QVariant::Type::Double );
1099 rat->appendField( QStringLiteral( "Class" ), Qgis::RasterAttributeTableFieldUsage::Name, QVariant::Type::String );
1100 rat->appendField( QStringLiteral( "RedMin" ), Qgis::RasterAttributeTableFieldUsage::RedMin, QVariant::Type::Int );
1101 rat->appendField( QStringLiteral( "GreenMin" ), Qgis::RasterAttributeTableFieldUsage::GreenMin, QVariant::Type::Int );
1102 rat->appendField( QStringLiteral( "BlueMin" ), Qgis::RasterAttributeTableFieldUsage::BlueMin, QVariant::Type::Int );
1103 rat->appendField( QStringLiteral( "AlphaMin" ), Qgis::RasterAttributeTableFieldUsage::AlphaMin, QVariant::Type::Int );
1104 rat->appendField( QStringLiteral( "RedMax" ), Qgis::RasterAttributeTableFieldUsage::RedMax, QVariant::Type::Int );
1105 rat->appendField( QStringLiteral( "GreenMax" ), Qgis::RasterAttributeTableFieldUsage::GreenMax, QVariant::Type::Int );
1106 rat->appendField( QStringLiteral( "BlueMax" ), Qgis::RasterAttributeTableFieldUsage::BlueMax, QVariant::Type::Int );
1107 rat->appendField( QStringLiteral( "AlphaMax" ), Qgis::RasterAttributeTableFieldUsage::AlphaMax, QVariant::Type::Int );
1108 const QList<QgsColorRampShader::ColorRampItem> rampItems { shaderFunction->colorRampItemList() };
1109 if ( rampItems.size() > 1 )
1110 {
1111 QColor color1 { rampItems.at( 0 ).color };
1112 QString label1 { rampItems.at( 0 ).label };
1113 QVariant value1( rampItems.at( 0 ).value );
1114 const int rampItemSize = rampItems.size();
1115 for ( int i = 1; i < rampItemSize; ++i )
1116 {
1117 const QgsColorRampShader::ColorRampItem &rampItem { rampItems.at( i )};
1118 rat->appendRow( QVariantList() << value1 << rampItem.value << QStringLiteral( "%1 - %2" ).arg( label1, rampItem.label ) << 0 << 0 << 0 << 255 << 0 << 0 << 0 << 255 );
1119 rat->setRamp( rat->data().length() - 1, color1, rampItem.color );
1120 label1 = rampItem.label;
1121 value1 = rampItem.value;
1122 color1 = rampItem.color;
1123 }
1124 }
1125 break;
1126 }
1127
1128 case QgsColorRampShader::Type::Discrete:
1129 {
1130 rat->appendField( QStringLiteral( "Min" ), Qgis::RasterAttributeTableFieldUsage::Min, QVariant::Type::Double );
1131 rat->appendField( QStringLiteral( "Max" ), Qgis::RasterAttributeTableFieldUsage::Max, QVariant::Type::Double );
1132 rat->appendField( QStringLiteral( "Class" ), Qgis::RasterAttributeTableFieldUsage::Name, QVariant::Type::String );
1133 rat->appendField( QStringLiteral( "Red" ), Qgis::RasterAttributeTableFieldUsage::Red, QVariant::Type::Int );
1134 rat->appendField( QStringLiteral( "Green" ), Qgis::RasterAttributeTableFieldUsage::Green, QVariant::Type::Int );
1135 rat->appendField( QStringLiteral( "Blue" ), Qgis::RasterAttributeTableFieldUsage::Blue, QVariant::Type::Int );
1136 rat->appendField( QStringLiteral( "Alpha" ), Qgis::RasterAttributeTableFieldUsage::Alpha, QVariant::Type::Int );
1137 const QList<QgsColorRampShader::ColorRampItem> rampItems { shaderFunction->colorRampItemList() };
1138 if ( rampItems.size( ) > 1 )
1139 {
1140 QColor color1 { rampItems.at( 0 ).color };
1141 QString label1 { rampItems.at( 0 ).label };
1142 QVariant value1( rampItems.at( 0 ).value );
1143 const int rampItemSize = rampItems.size();
1144 for ( int i = 1; i < rampItemSize; ++i )
1145 {
1146 const QgsColorRampShader::ColorRampItem &rampItem { rampItems.at( i )};
1147 rat->appendRow( QVariantList() << value1 << rampItem.value << QStringLiteral( "%1 - %2" ).arg( label1, rampItem.label ) << 0 << 0 << 0 << 255 << 0 << 0 << 0 << 255 );
1148 rat->setRamp( rat->data().length() - 1, color1, rampItem.color );
1149 label1 = rampItem.label;
1150 value1 = rampItem.value;
1151 color1 = rampItem.color;
1152 }
1153 }
1154 break;
1155 }
1156
1157 case QgsColorRampShader::Type::Exact:
1158 {
1159 rat->appendField( QStringLiteral( "Value" ), Qgis::RasterAttributeTableFieldUsage::MinMax, QVariant::Type::Double );
1160 rat->appendField( QStringLiteral( "Class" ), Qgis::RasterAttributeTableFieldUsage::Name, QVariant::Type::String );
1161 rat->appendField( QStringLiteral( "Red" ), Qgis::RasterAttributeTableFieldUsage::Red, QVariant::Type::Int );
1162 rat->appendField( QStringLiteral( "Green" ), Qgis::RasterAttributeTableFieldUsage::Green, QVariant::Type::Int );
1163 rat->appendField( QStringLiteral( "Blue" ), Qgis::RasterAttributeTableFieldUsage::Blue, QVariant::Type::Int );
1164 rat->appendField( QStringLiteral( "Alpha" ), Qgis::RasterAttributeTableFieldUsage::Alpha, QVariant::Type::Int );
1165 const QList<QgsColorRampShader::ColorRampItem> rampItems { shaderFunction->colorRampItemList() };
1166 for ( const QgsColorRampShader::ColorRampItem &rampItem : std::as_const( rampItems ) )
1167 {
1168 rat->appendRow( QVariantList() << rampItem.value << rampItem.label << 0 << 0 << 0 << 255 );
1169 rat->setColor( rat->data().length() - 1, rampItem.color );
1170 }
1171 break;
1172 }
1173 }
1174
1175 if ( bandNumber )
1176 {
1177 *bandNumber = pseudoColorRenderer->inputBand();
1178 }
1179
1180 return rat;
1181 }
1182 else
1183 {
1184 return nullptr;
1185 }
1186 }
1187 else
1188 {
1189 return nullptr;
1190 }
1191 }
1192 else
1193 {
1194 return nullptr;
1195 }
1196}
1197
1198QHash<Qgis::RasterAttributeTableFieldUsage, QgsRasterAttributeTable::UsageInformation> QgsRasterAttributeTable::usageInformation()
1199{
1200 std::call_once( usageInformationLoaderFlag, [ ]
1201 {
1202 QgsRasterAttributeTable::sUsageInformation.insert( Qgis::RasterAttributeTableFieldUsage::Generic, { tr( "General Purpose Field" ), false, false, false, false, true, true, QList<QVariant::Type>() << QVariant::String << QVariant::Int << QVariant::LongLong << QVariant::Double } );
1203 QgsRasterAttributeTable::sUsageInformation.insert( Qgis::RasterAttributeTableFieldUsage::PixelCount, { tr( "Histogram Pixel Count" ), true, false, false, false, true, false, QList<QVariant::Type>() << QVariant::LongLong } );
1204 QgsRasterAttributeTable::sUsageInformation.insert( Qgis::RasterAttributeTableFieldUsage::Name, { tr( "Class Name" ), false, false, false, false, true, true, QList<QVariant::Type>() << QVariant::String } );
1205 QgsRasterAttributeTable::sUsageInformation.insert( Qgis::RasterAttributeTableFieldUsage::MinMax, { tr( "Class Value (min=max)" ), true, true, false, false, true, false, QList<QVariant::Type>() << QVariant::Int << QVariant::LongLong << QVariant::Double } );
1206 QgsRasterAttributeTable::sUsageInformation.insert( Qgis::RasterAttributeTableFieldUsage::Min, { tr( "Class Minimum Value" ), true, true, false, false, true, false, QList<QVariant::Type>() << QVariant::Int << QVariant::LongLong << QVariant::Double } );
1207 QgsRasterAttributeTable::sUsageInformation.insert( Qgis::RasterAttributeTableFieldUsage::Max, { tr( "Class Maximum Value" ), true, true, false, false, true, false, QList<QVariant::Type>() << QVariant::Int << QVariant::LongLong << QVariant::Double } );
1208 QgsRasterAttributeTable::sUsageInformation.insert( Qgis::RasterAttributeTableFieldUsage::Red, { tr( "Red Color Value (0-255)" ), true, false, true, false, true, false, QList<QVariant::Type>() << QVariant::Int } );
1209 QgsRasterAttributeTable::sUsageInformation.insert( Qgis::RasterAttributeTableFieldUsage::Green, { tr( "Green Color Value (0-255)" ), true, false, true, false, true, false, QList<QVariant::Type>() << QVariant::Int } );
1210 QgsRasterAttributeTable::sUsageInformation.insert( Qgis::RasterAttributeTableFieldUsage::Blue, { tr( "Blue Color Value (0-255)" ), true, false, true, false, true, false, QList<QVariant::Type>() << QVariant::Int } );
1211 QgsRasterAttributeTable::sUsageInformation.insert( Qgis::RasterAttributeTableFieldUsage::Alpha, { tr( "Alpha Color Value (0-255)" ), true, false, true, false, true, false, QList<QVariant::Type>() << QVariant::Int } );
1212 QgsRasterAttributeTable::sUsageInformation.insert( Qgis::RasterAttributeTableFieldUsage::RedMin, { tr( "Red Color Minimum Value (0-255)" ), true, false, false, true, true, false, QList<QVariant::Type>() << QVariant::Int } );
1213 QgsRasterAttributeTable::sUsageInformation.insert( Qgis::RasterAttributeTableFieldUsage::GreenMin, { tr( "Green Color Minimum Value (0-255)" ), true, false, false, true, true, false, QList<QVariant::Type>() << QVariant::Int } );
1214 QgsRasterAttributeTable::sUsageInformation.insert( Qgis::RasterAttributeTableFieldUsage::BlueMin, { tr( "Blue Color Minimum Value (0-255)" ), true, false, false, true, true, false, QList<QVariant::Type>() << QVariant::Int } );
1215 QgsRasterAttributeTable::sUsageInformation.insert( Qgis::RasterAttributeTableFieldUsage::AlphaMin, { tr( "Alpha Color Minimum Value (0-255)" ), true, false, false, true, true, false, QList<QVariant::Type>() << QVariant::Int } );
1216 QgsRasterAttributeTable::sUsageInformation.insert( Qgis::RasterAttributeTableFieldUsage::RedMax, { tr( "Red Color Minimum Value (0-255)" ), true, false, false, true, true, false, QList<QVariant::Type>() << QVariant::Int } );
1217 QgsRasterAttributeTable::sUsageInformation.insert( Qgis::RasterAttributeTableFieldUsage::GreenMax, { tr( "Green Color Minimum Value (0-255)" ), true, false, false, true, true, false, QList<QVariant::Type>() << QVariant::Int } );
1218 QgsRasterAttributeTable::sUsageInformation.insert( Qgis::RasterAttributeTableFieldUsage::BlueMax, { tr( "Blue Color Minimum Value (0-255)" ), true, false, false, true, true, false, QList<QVariant::Type>() << QVariant::Int } );
1219 QgsRasterAttributeTable::sUsageInformation.insert( Qgis::RasterAttributeTableFieldUsage::AlphaMax, { tr( "Alpha Color Minimum Value (0-255)" ), true, false, false, true, true, false, QList<QVariant::Type>() << QVariant::Int } );
1220 // Unsupported!!
1221 QgsRasterAttributeTable::sUsageInformation.insert( Qgis::RasterAttributeTableFieldUsage::MaxCount, { tr( "Maximum GFU value(equals to GFU_AlphaMax+1 currently)" ), true, false, false, true, false, false, QList<QVariant::Type>() << QVariant::Int } );
1222 } );
1223 return QgsRasterAttributeTable::sUsageInformation;
1224}
1225
1226void QgsRasterAttributeTable::setType()
1227{
1228 const QList<Qgis::RasterAttributeTableFieldUsage> fieldUsages { usages() };
1230}
1231
1232
1234QHash<int, QgsRasterAttributeTable::UsageInformation> QgsRasterAttributeTable::usageInformationInt()
1235{
1236 QHash<int, QgsRasterAttributeTable::UsageInformation> usageInfoInt;
1237 const QHash<Qgis::RasterAttributeTableFieldUsage, QgsRasterAttributeTable::UsageInformation> usageInfo { QgsRasterAttributeTable::usageInformation() };
1238 for ( auto it = usageInfo.cbegin(); it != usageInfo.cend(); ++it )
1239 {
1240 usageInfoInt.insert( static_cast<int>( it.key() ), it.value() );
1241 }
1242 return usageInfoInt;
1243}
1245
1247{
1248 return mFilePath;
1249}
1250
1251QList<QgsRasterAttributeTable::MinMaxClass> QgsRasterAttributeTable::minMaxClasses( const int classificationColumn ) const
1252{
1253 QList<QgsRasterAttributeTable::MinMaxClass> classes;
1254 if ( !isValid() )
1255 {
1256 QgsDebugError( "minMaxClasses was called on an invalid RAT" );
1257 return classes;
1258 }
1259
1260 const QList<Qgis::RasterAttributeTableFieldUsage> fieldUsages { usages() };
1261
1262 if ( ! fieldUsages.contains( Qgis::RasterAttributeTableFieldUsage::MinMax ) )
1263 {
1264 QgsDebugError( "minMaxClasses was called on a ramp raster" );
1265 return classes;
1266 }
1267
1268 const int minMaxIndex { static_cast<int>( fieldUsages.indexOf( Qgis::RasterAttributeTableFieldUsage::MinMax ) ) };
1269
1270 Q_ASSERT( minMaxIndex >= 0 );
1271
1272 int classificationIndex = classificationColumn;
1273 if ( classificationIndex >= 0 && classificationIndex < mFields.count( ) )
1274 {
1275 const Field classificationField { mFields.at( classificationIndex ) };
1276 if ( ( classificationField.usage != Qgis::RasterAttributeTableFieldUsage::Name && classificationField.usage != Qgis::RasterAttributeTableFieldUsage::Generic ) )
1277 {
1278 QgsDebugError( "minMaxClasses was called with a classification column which is not suitable for classification" );
1279 return classes;
1280 }
1281 }
1282 else if ( classificationIndex == -1 ) // Special value for not-set
1283 {
1284 // Find first value or generic field
1285 if ( fieldUsages.contains( Qgis::RasterAttributeTableFieldUsage::Name ) )
1286 {
1287 classificationIndex = fieldUsages.indexOf( Qgis::RasterAttributeTableFieldUsage::Name );
1288 }
1289 else if ( fieldUsages.contains( Qgis::RasterAttributeTableFieldUsage::Generic ) )
1290 {
1291 classificationIndex = fieldUsages.indexOf( Qgis::RasterAttributeTableFieldUsage::Generic );
1292 }
1293 else
1294 {
1295 classificationIndex = minMaxIndex;
1296 }
1297 }
1298 else if ( classificationIndex >= mFields.count( ) )
1299 {
1300 QgsDebugError( "minMaxClasses was called with a classification column out of range" );
1301 return classes;
1302 }
1303
1304 if ( classificationIndex >= 0 )
1305 {
1306 QStringList labels;
1307 int rowIdx { 0 };
1308 for ( const QVariantList &row : std::as_const( mData ) )
1309 {
1310 const QString label { row.at( classificationIndex ).toString() };
1311 bool ok;
1312 const double value { row.at( minMaxIndex ).toDouble( &ok ) };
1313 // This should never happen, could eventually become a Q_ASSERT
1314 if ( ! ok )
1315 {
1316 QgsDebugError( "minMaxClasses could not convert a MinMax value to double" );
1317 return classes;
1318 }
1319 if ( labels.contains( label ) )
1320 {
1321 classes[ labels.indexOf( label ) ].minMaxValues.push_back( value );
1322 }
1323 else
1324 {
1325 labels.push_back( label );
1326 classes.push_back( { label, { value }, color( rowIdx ) } );
1327 }
1328 rowIdx++;
1329 }
1330 }
1331 return classes;
1332}
1333
1334QgsGradientColorRamp QgsRasterAttributeTable::colorRamp( QStringList &labels, const int labelColumn ) const
1335{
1336 QgsGradientColorRamp ramp{ Qt::GlobalColor::white, Qt::GlobalColor::black };
1337 const QList<Qgis::RasterAttributeTableFieldUsage> fieldUsages { usages() };
1338 const int minIdx { static_cast<int>( fieldUsages.indexOf( Qgis::RasterAttributeTableFieldUsage::Min ) ) };
1339 const int maxIdx { static_cast<int>( fieldUsages.indexOf( Qgis::RasterAttributeTableFieldUsage::Max ) ) };
1340 const bool isRange { minIdx >= 0 && maxIdx >= 0 };
1341
1342 int labelIdx { labelColumn };
1343 if ( labelColumn < 0 || labelColumn >= fields().count( ) ||
1344 ( fieldUsages.at( labelColumn ) != Qgis::RasterAttributeTableFieldUsage::Name && fieldUsages.at( labelColumn ) != Qgis::RasterAttributeTableFieldUsage::Generic ) )
1345 {
1346 labelIdx = -1;
1347 }
1348
1349 if ( ! mData.isEmpty() && ( minIdx >= 0 && maxIdx >= 0 ) )
1350 {
1352 const bool hasColorOrRamp { hasColor() || hasRamp() };
1353 {
1354
1355 const double min { minimumValue() };
1356 const double max { maximumValue() };
1357 const double range { max - min };
1358
1359 if ( range != 0 )
1360 {
1361
1362 if ( ! isnan( min ) && ! isnan( max ) )
1363 {
1364 const QList<QVariantList> dataCopy( orderedRows() );
1365
1366 QgsRasterAttributeTable orderedRat;
1367 for ( const Field &f : std::as_const( mFields ) )
1368 {
1369 orderedRat.appendField( f );
1370 }
1371 for ( const QVariantList &r : std::as_const( dataCopy ) )
1372 {
1373 orderedRat.appendRow( r );
1374 }
1375
1376 QColor lastColor { ramp.color1() };
1377
1378 if ( hasColorOrRamp )
1379 {
1380 ramp.setColor1( orderedRat.hasColor() ? orderedRat.color( 0 ) : orderedRat.ramp( 0 ).color1() );
1381 ramp.setColor2( orderedRat.hasColor() ? orderedRat.color( orderedRat.data().count() - 1 ) : orderedRat.ramp( orderedRat.data().count() - 1 ).color2() );
1382 lastColor = orderedRat.hasColor() ? orderedRat.color( 0 ) : orderedRat.ramp( 0 ).color2();
1383 }
1384
1385 auto labelFromField = [ & ]( int rowIdx ) -> QString
1386 {
1387 if ( labelIdx < 0 )
1388 {
1389 return QStringLiteral( "%L1 - %L2" ).arg( orderedRat.value( rowIdx, minIdx ).toDouble() ).arg( orderedRat.value( rowIdx, maxIdx ).toDouble() );
1390 }
1391 const QVariant val( orderedRat.value( rowIdx, labelIdx ) );
1392 bool ok { true };
1393 QString res;
1394 switch ( val.type() )
1395 {
1396 case QVariant::Type::Char:
1397 return QString( val.toChar() );
1398 case QVariant::Type::Int:
1399 res = QLocale().toString( val.toInt( &ok ) );
1400 break;
1401 case QVariant::Type::LongLong:
1402 res = QLocale().toString( val.toLongLong( &ok ) );
1403 break;
1404 case QVariant::Type::UInt:
1405 res = QLocale().toString( val.toUInt( &ok ) );
1406 break;
1407 case QVariant::Type::ULongLong:
1408 res = QLocale().toString( val.toULongLong( &ok ) );
1409 break;
1410 case QVariant::Type::Double:
1411 res = QLocale().toString( val.toDouble( &ok ), 'g' );
1412 break;
1413 case QVariant::Type::String:
1414 default:
1415 return val.toString( );
1416 }
1417 return ok ? res : val.toString();
1418 };
1419
1420 // Case 1: range classes, discrete colors
1421 // - create stops for the lower value of each class except for the first,
1422 // - use the color from the previous class
1423 if ( orderedRat.hasColor() && isRange )
1424 {
1425 labels.push_back( labelFromField( 0 ) );
1426
1427 for ( int rowIdx = 1; rowIdx < orderedRat.data().count(); ++rowIdx )
1428 {
1429 const double offset { ( orderedRat.value( rowIdx, minIdx ).toDouble( ) - min ) / range };
1430 const QColor color { orderedRat.color( rowIdx - 1 ) };
1431 stops.append( QgsGradientStop( offset, color ) );
1432 labels.push_back( labelFromField( rowIdx ) );
1433 }
1434 }
1435 // Case 2: range classes, gradients colors
1436 // Take the class bounds (average value between max of previous class and min of the next)
1437 // to avoid potential overlapping or gaps between classes.
1438 // Create stop:
1439 // - first stop at value taking the max color of the previous class
1440 // - second stop at value + epsilon taking the min color of the next class, unless colors and offset are equal
1441 else if ( orderedRat.hasRamp() && isRange )
1442 {
1443 double prevOffset { 0 };
1444 labels.push_back( labelFromField( 0 ) );
1445 for ( int rowIdx = 1; rowIdx < orderedRat.data().count(); ++rowIdx )
1446 {
1447 labels.push_back( labelFromField( rowIdx ) );
1448 const int prevRowIdx { rowIdx - 1 };
1449 const double offset { ( ( orderedRat.value( rowIdx, minIdx ).toDouble( ) + orderedRat.value( prevRowIdx, maxIdx ).toDouble( ) ) / 2.0 - min ) / range };
1450 const QgsGradientColorRamp previousRamp { orderedRat.ramp( prevRowIdx ) };
1451 stops.append( QgsGradientStop( offset, previousRamp.color2() ) );
1452
1453 const QgsGradientColorRamp currentRamp { orderedRat.ramp( rowIdx ) };
1454 // An additional stop is added if the colors are different or offsets are different by 1e-6 (offset varies from 0 to 1).
1455 if ( currentRamp.color1() != previousRamp.color2() && qgsDoubleNear( offset, prevOffset, 1e-6 ) )
1456 {
1457 stops.append( QgsGradientStop( offset + std::numeric_limits<double>::epsilon(), currentRamp.color1() ) );
1458 }
1459 prevOffset = offset;
1460 }
1461 }
1462 // Case 3: range classes but no colors at all
1463 // Take the class borders (average value between max of previous class and min of the next)
1464 // Create stop for the lower class, actually skipping the upper bound of the last class
1465 else
1466 {
1467 labels.push_back( labelFromField( 0 ) );
1468
1469 for ( int rowIdx = 1; rowIdx < orderedRat.data().count(); ++rowIdx )
1470 {
1471 const int prevRowIdx { rowIdx - 1 };
1472 const double offset { ( ( orderedRat.value( rowIdx, minIdx ).toDouble( ) + orderedRat.value( prevRowIdx, maxIdx ).toDouble( ) ) / 2.0 - min ) / range };
1473 stops.append( QgsGradientStop( offset, ramp.color( offset ) ) );
1474 labels.push_back( labelFromField( rowIdx ) );
1475 }
1476 }
1477 }
1478 }
1479 }
1480
1481 ramp.setStops( stops );
1483
1484 }
1485
1486 return ramp;
1487}
1488
1489QgsRasterRenderer *QgsRasterAttributeTable::createRenderer( QgsRasterDataProvider *provider, const int bandNumber, const int classificationColumn )
1490{
1491 if ( ! provider )
1492 {
1493 return nullptr;
1494 }
1495
1496 std::unique_ptr<QgsRasterRenderer> renderer;
1497
1499 {
1500 std::unique_ptr<QgsColorRamp> ramp;
1501 if ( ! hasColor() )
1502 {
1503 ramp.reset( new QgsRandomColorRamp() );
1504 }
1506 if ( classes.isEmpty() )
1507 return nullptr;
1508
1509 renderer = std::make_unique<QgsPalettedRasterRenderer>( provider,
1510 bandNumber,
1511 classes );
1512 }
1513 else
1514 {
1515 std::unique_ptr<QgsSingleBandPseudoColorRenderer> pseudoColorRenderer = std::make_unique<QgsSingleBandPseudoColorRenderer>( provider, bandNumber );
1516 QStringList labels;
1517 // Athematic classification is not supported, but the classificationColumn will be used for labels.
1518 // if it's not specified, try to guess it here.
1519 int labelColumn { classificationColumn };
1520 if ( labelColumn < 0 )
1521 {
1522 const QList<Qgis::RasterAttributeTableFieldUsage> fieldUsages { usages() };
1523 labelColumn = fieldUsages.indexOf( Qgis::RasterAttributeTableFieldUsage::Name );
1524 if ( labelColumn < 0 )
1525 {
1526 labelColumn = fieldUsages.indexOf( Qgis::RasterAttributeTableFieldUsage::Generic );
1527 }
1528 }
1529 QgsGradientColorRamp *ramp { colorRamp( labels, labelColumn ).clone() };
1530 pseudoColorRenderer->setClassificationMin( minimumValue() );
1531 pseudoColorRenderer->setClassificationMax( maximumValue() );
1532 // Use discrete for single colors, interpolated for ramps
1533 pseudoColorRenderer->createShader( ramp, hasRamp() ? QgsColorRampShader::Type::Interpolated : QgsColorRampShader::Type::Discrete, QgsColorRampShader::ClassificationMode::Continuous, ramp->stops().count() + 2, true );
1534 if ( pseudoColorRenderer->shader() )
1535 {
1536 pseudoColorRenderer->shader()->setMaximumValue( maximumValue() );
1537 pseudoColorRenderer->shader()->setMinimumValue( minimumValue() );
1538 // Set labels
1539 if ( QgsColorRampShader *shaderFunction = static_cast<QgsColorRampShader *>( pseudoColorRenderer->shader()->rasterShaderFunction() ) )
1540 {
1541 shaderFunction->setMinimumValue( minimumValue() );
1542 shaderFunction->setMaximumValue( maximumValue() );
1543 const bool labelsAreUsable { ramp->count() > 2 && labels.count() == ramp->count() - 1 };
1544
1545 if ( labelsAreUsable )
1546 {
1547 QList<QgsColorRampShader::ColorRampItem> newItemList;
1548 const double range { maximumValue() - minimumValue() };
1549 int stopIdx { 0 };
1550 for ( const QString &label : std::as_const( labels ) )
1551 {
1552 if ( stopIdx >= ramp->count() - 2 )
1553 {
1554 break;
1555 }
1556 double value { minimumValue() + ramp->stops().at( stopIdx ).offset * range };
1557 QgsColorRampShader::ColorRampItem item { value, ramp->stops().at( stopIdx ).color, label };
1558 newItemList.push_back( item );
1559 stopIdx++;
1560 }
1561
1562 QgsColorRampShader::ColorRampItem item { maximumValue(), ramp->color2(), labels.last() };
1563 newItemList.push_back( item );
1564 shaderFunction->setColorRampItemList( newItemList );
1565 }
1566 }
1567 }
1568 renderer.reset( pseudoColorRenderer.release() );
1569 }
1570
1571 return renderer.release();
1572}
1573
1574QList<QList<QVariant> > QgsRasterAttributeTable::orderedRows() const
1575{
1576 const QList<Qgis::RasterAttributeTableFieldUsage> fieldUsages { usages() };
1577 const int minIdx { static_cast<int>( fieldUsages.indexOf( Qgis::RasterAttributeTableFieldUsage::Min ) ) };
1578 const int maxIdx { static_cast<int>( fieldUsages.indexOf( Qgis::RasterAttributeTableFieldUsage::Max ) ) };
1579 const bool isRange { minIdx >= 0 && maxIdx >= 0 };
1580 QList<QVariantList> dataCopy( mData );
1581
1582 if ( isRange )
1583 {
1584 std::sort( dataCopy.begin(), dataCopy.end(), [ & ]( const QVariantList & first, const QVariantList & second ) -> bool
1585 {
1586 return ( first.at( maxIdx ).toDouble() + first.at( minIdx ).toDouble() ) < ( second.at( maxIdx ).toDouble() + second.at( minIdx ).toDouble() );
1587 } );
1588 }
1589 else
1590 {
1591 const int minMaxIdx { static_cast<int>( fieldUsages.indexOf( Qgis::RasterAttributeTableFieldUsage::MinMax ) ) };
1592 if ( minMaxIdx < 0 )
1593 {
1594 return dataCopy;
1595 }
1596 else
1597 {
1598 std::sort( dataCopy.begin(), dataCopy.end(), [ & ]( const QVariantList & first, const QVariantList & second ) -> bool
1599 {
1600 return first.at( minMaxIdx ).toDouble() < second.at( minMaxIdx ).toDouble();
1601 } );
1602 }
1603 }
1604
1605 return dataCopy;
1606}
1607
1609{
1611}
1612
1614{
1616}
RasterAttributeTableType
The RasterAttributeTableType enum represents the type of RAT.
Definition: qgis.h:1220
RasterAttributeTableFieldUsage
The RasterAttributeTableFieldUsage enum represents the usage of a Raster Attribute Table field.
Definition: qgis.h:1191
@ PixelCount
Field usage PixelCount.
@ AlphaMax
Field usage AlphaMax.
@ GreenMax
Field usage GreenMax.
@ AlphaMin
Field usage AlphaMin.
@ MaxCount
Not used by QGIS: GDAL Maximum GFU value (equals to GFU_AlphaMax+1 currently)
@ GreenMin
Field usage GreenMin.
@ NoGeometry
No geometry.
A vector of attributes.
Definition: qgsattributes.h:59
A ramp shader will color a raster pixel based on a list of values ranges in a ramp.
This class represents a coordinate reference system (CRS).
Contains information about the context in which a coordinate transform is executed.
Wrapper for iterator of features from vector data provider or vector layer.
This class wraps a request for features to a vector layer (or directly its vector data provider).
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition: qgsfeature.h:56
QgsAttributes attributes
Definition: qgsfeature.h:65
int attributeCount() const
Returns the number of attributes attached to the feature.
Definition: qgsfeature.cpp:155
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:53
Container of fields for a vector layer.
Definition: qgsfields.h:45
bool append(const QgsField &field, FieldOrigin origin=OriginProvider, int originIndex=-1)
Appends a field. The field must have unique name, otherwise it is rejected (returns false)
Definition: qgsfields.cpp:59
static QString ensureFileNameHasExtension(const QString &fileName, const QStringList &extensions)
Ensures that a fileName ends with an extension from the provided list of extensions.
Gradient color ramp, which smoothly interpolates between two colors and also supports optional extra ...
void setColor1(const QColor &color)
Sets the gradient start color.
void setColor2(const QColor &color)
Sets the gradient end color.
int count() const override
Returns number of defined colors, or -1 if undefined.
QColor color(double value) const override
Returns the color corresponding to a specified value.
void setStops(const QgsGradientStopsList &stops)
Sets the list of intermediate gradient stops for the ramp.
QColor color1() const
Returns the gradient start color.
QgsGradientStopsList stops() const
Returns the list of intermediate gradient stops for the ramp.
QgsGradientColorRamp * clone() const override
Creates a clone of the color ramp.
void setDiscrete(bool discrete)
Sets whether the gradient should use discrete interpolation, rather than smoothly interpolating betwe...
QColor color2() const
Returns the gradient end color.
Represents a color stop within a QgsGradientColorRamp color ramp.
bool isValid
Definition: qgsmaplayer.h:83
Renderer for paletted raster images.
static QgsPalettedRasterRenderer::MultiValueClassData rasterAttributeTableToClassData(const QgsRasterAttributeTable *attributeTable, int classificationColumn=-1, QgsColorRamp *ramp=nullptr)
Reads and returns classes from the Raster Attribute Table attributeTable, optionally classifying the ...
QList< QgsPalettedRasterRenderer::Class > ClassData
Map of value to class properties.
QList< QgsPalettedRasterRenderer::MultiValueClass > MultiValueClassData
Map of multi value to class properties.
Totally random color ramp.
The Field class represents a Raster Attribute Table field, including its name, usage and type.
Qgis::RasterAttributeTableFieldUsage usage
bool isRamp() const
Returns true if the field carries a color ramp component information (RedMin/RedMax,...
bool isColor() const
Returns true if the field carries a color component (Red, Green, Blue and optionally Alpha) informati...
The QgsRasterAttributeTable class represents a Raster Attribute Table (RAT).
const QgsRasterAttributeTable::Field fieldByName(const QString name, bool *ok=nullptr) const
Returns a field by name or a default constructed field with empty name if the field is not found.
bool isDirty() const
Returns true if the Raster Attribute Table was modified from its last reading from the storage.
bool setColor(const int row, const QColor &color)
Sets the color for the row at rowIndex to color.
QList< QgsRasterAttributeTable::MinMaxClass > minMaxClasses(const int classificationColumn=-1) const
Returns the classes for a thematic Raster Attribute Table, classified by classificationColumn,...
QgsGradientColorRamp ramp(int row) const
Returns the gradient color ramp of the rat row or a default constructed gradient if row does not exis...
QgsRasterRenderer * createRenderer(QgsRasterDataProvider *provider, const int bandNumber, const int classificationColumn=-1)
Creates and returns a (possibly nullptr) raster renderer for the specified provider and bandNumber an...
bool readFromFile(const QString &path, QString *errorMessage=nullptr)
Reads the Raster Attribute Table from a DBF file specified by path, optionally reporting any error in...
QgsGradientColorRamp colorRamp(QStringList &labels, const int labelColumn=-1) const
Returns the color ramp for an athematic Raster Attribute Table setting the labels in labels,...
bool insertField(int position, const QgsRasterAttributeTable::Field &field, QString *errorMessage=nullptr)
Inserts a new field at position, optionally reporting any error in errorMessage, returns true on succ...
bool hasColor() const
Returns true if the Raster Attribute Table has color RGBA information.
bool setValue(const int row, const int column, const QVariant &value)
Sets the value for row and column.
static QList< Qgis::RasterAttributeTableFieldUsage > valueAndColorFieldUsages()
Returns the list of field usages for colors and values.
QList< QgsRasterAttributeTable::Field > fields() const
Returns the Raster Attribute Table fields.
PRIVATE QColor color(int row) const
Returns the color of the rat row or an invalid color if row does not exist or if there is no color de...
static QgsRasterAttributeTable * createFromRaster(QgsRasterLayer *rasterLayer, int *bandNumber=nullptr)
Creates a new Raster Attribute Table from a raster layer, the renderer must be Paletted or SingleBand...
bool removeRow(int position=0, QString *errorMessage=nullptr)
Removes the row in the Raster Attribute Table at position, optionally reporting any error in errorMes...
bool appendRow(const QVariantList &data, QString *errorMessage=nullptr)
Appends a row of data to the RAT, optionally reporting any error in errorMessage, returns true on suc...
static QHash< Qgis::RasterAttributeTableFieldUsage, QgsRasterAttributeTable::UsageInformation > usageInformation()
Returns information about supported Raster Attribute Table usages.
QList< Qgis::RasterAttributeTableFieldUsage > usages() const
Returns the list of field usages.
QVariantList row(const double matchValue) const
Returns a row of data for the given matchValue or and empty row if there is not match.
bool insertRow(int position, const QVariantList &rowData, QString *errorMessage=nullptr)
Inserts a row of rowData in the Raster Attribute Table at position, optionally reporting any error in...
bool writeToFile(const QString &path, QString *errorMessage=nullptr)
Writes the Raster Attribute Table to a DBF file specified by path, optionally reporting any error in ...
double minimumValue() const
Returns the minimum value of the MinMax (thematic) or Min (athematic) column, returns NaN on errors.
double maximumValue() const
Returns the maximum value of the MinMax (thematic) or Max (athematic) column, returns NaN on errors.
const QList< QgsRasterAttributeTable::Field > fieldsByUsage(const Qgis::RasterAttributeTableFieldUsage fieldUsage) const
Returns the list of fields matching fieldUsage.
void setDirty(bool isDirty)
Sets the Raster Attribute Table dirty state to isDirty;.
bool insertColor(int position, QString *errorMessage=nullptr)
Create RGBA fields and inserts them at position, optionally reporting any error in errorMessage,...
bool isValid(QString *errorMessage=nullptr) const
Returns true if the Raster Attribute Table is valid, optionally reporting validity checks results in ...
static Qgis::RasterAttributeTableFieldUsage guessFieldUsage(const QString &name, const QVariant::Type type)
Try to determine the field usage from its name and type.
const QList< QList< QVariant > > data() const
Returns the Raster Attribute Table rows.
Qgis::RasterAttributeTableType type() const
Returns the Raster Attribute Table type.
bool setRamp(const int row, const QColor &colorMin, const QColor &colorMax)
Sets the color ramp for the row at rowIndex to colorMin and colorMax.
QList< QList< QVariant > > orderedRows() const
Returns the data rows ordered by the value column(s) in ascending order, if the attribute table type ...
static QString usageName(const Qgis::RasterAttributeTableFieldUsage fieldusage)
Returns the translated human readable name of fieldUsage.
bool hasRamp() const
Returns true if the Raster Attribute Table has ramp RGBA information.
QgsFeatureList qgisFeatures() const
Returns the Raster Attribute Table rows as a list of QgsFeature.
bool setFieldUsage(int fieldIndex, const Qgis::RasterAttributeTableFieldUsage usage)
Change the usage of the field at index fieldIndex to usage with checks for allowed types.
QString filePath() const
Returns the (possibly empty) path of the file-based RAT, the path is set when a RAT is read or writte...
bool insertRamp(int position, QString *errorMessage=nullptr)
Create RGBA minimum and maximum fields and inserts them at position, optionally reporting any error i...
bool appendField(const QString &name, const Qgis::RasterAttributeTableFieldUsage usage, const QVariant::Type type, QString *errorMessage=nullptr)
Creates a new field from name, usage and type and appends it to the fields, optionally reporting any ...
QVariant value(const int row, const int column) const
Returns the value for row and column.
QgsFields qgisFields() const
Returns the Raster Attribute Table fields as QgsFields.
bool removeField(const QString &name, QString *errorMessage=nullptr)
Removes the field with name, optionally reporting any error in errorMessage, returns true on success.
Base class for raster data providers.
Represents a raster layer.
QgsRasterRenderer * renderer() const
Returns the raster's renderer.
QgsRasterDataProvider * dataProvider() override
Returns the source data provider.
Raster renderer pipe that applies colors to a raster.
Interface for all raster shaders.
Raster renderer pipe for single band pseudocolor.
Options to pass to writeAsVectorFormat()
QStringList layerOptions
List of OGR layer creation options.
QgsVectorFileWriter::ActionOnExistingFile actionOnExistingFile
Action on existing file.
static QgsVectorFileWriter * create(const QString &fileName, const QgsFields &fields, Qgis::WkbType geometryType, const QgsCoordinateReferenceSystem &srs, const QgsCoordinateTransformContext &transformContext, const QgsVectorFileWriter::SaveVectorOptions &options, QgsFeatureSink::SinkFlags sinkFlags=QgsFeatureSink::SinkFlags(), QString *newFilename=nullptr, QString *newLayer=nullptr)
Create a new vector file writer.
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:5207
QList< QgsGradientStop > QgsGradientStopsList
List of gradient stops.
QList< QgsFeature > QgsFeatureList
Definition: qgsfeature.h:917
#define QgsDebugError(str)
Definition: qgslogger.h:38
Setting options for creating vector data providers.
Properties of a single value class.