QGIS API Documentation 3.30.0-'s-Hertogenbosch (f186b8efe0)
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 {
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->band();
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.count( ) > 1 )
1110 {
1111 QColor color1 { rampItems.at( 0 ).color };
1112 QString label1 { rampItems.at( 0 ).label };
1113 QVariant value1( rampItems.at( 0 ).value );
1114 for ( int i = 1; i < rampItems.count( ); ++i )
1115 {
1116 const QgsColorRampShader::ColorRampItem &rampItem { rampItems.at( i )};
1117 rat->appendRow( QVariantList() << value1 << rampItem.value << QStringLiteral( "%1 - %2" ).arg( label1, rampItem.label ) << 0 << 0 << 0 << 255 << 0 << 0 << 0 << 255 );
1118 rat->setRamp( rat->data().length() - 1, color1, rampItem.color );
1119 label1 = rampItem.label;
1120 value1 = rampItem.value;
1121 color1 = rampItem.color;
1122 }
1123 }
1124 break;
1125 }
1126
1127 case QgsColorRampShader::Type::Discrete:
1128 {
1129 rat->appendField( QStringLiteral( "Min" ), Qgis::RasterAttributeTableFieldUsage::Min, QVariant::Type::Double );
1130 rat->appendField( QStringLiteral( "Max" ), Qgis::RasterAttributeTableFieldUsage::Max, QVariant::Type::Double );
1131 rat->appendField( QStringLiteral( "Class" ), Qgis::RasterAttributeTableFieldUsage::Name, QVariant::Type::String );
1132 rat->appendField( QStringLiteral( "Red" ), Qgis::RasterAttributeTableFieldUsage::Red, QVariant::Type::Int );
1133 rat->appendField( QStringLiteral( "Green" ), Qgis::RasterAttributeTableFieldUsage::Green, QVariant::Type::Int );
1134 rat->appendField( QStringLiteral( "Blue" ), Qgis::RasterAttributeTableFieldUsage::Blue, QVariant::Type::Int );
1135 rat->appendField( QStringLiteral( "Alpha" ), Qgis::RasterAttributeTableFieldUsage::Alpha, QVariant::Type::Int );
1136 const QList<QgsColorRampShader::ColorRampItem> rampItems { shaderFunction->colorRampItemList() };
1137 if ( rampItems.count( ) > 1 )
1138 {
1139 QColor color1 { rampItems.at( 0 ).color };
1140 QString label1 { rampItems.at( 0 ).label };
1141 QVariant value1( rampItems.at( 0 ).value );
1142 for ( int i = 1; i < rampItems.count( ); ++i )
1143 {
1144 const QgsColorRampShader::ColorRampItem &rampItem { rampItems.at( i )};
1145 rat->appendRow( QVariantList() << value1 << rampItem.value << QStringLiteral( "%1 - %2" ).arg( label1, rampItem.label ) << 0 << 0 << 0 << 255 << 0 << 0 << 0 << 255 );
1146 rat->setRamp( rat->data().length() - 1, color1, rampItem.color );
1147 label1 = rampItem.label;
1148 value1 = rampItem.value;
1149 color1 = rampItem.color;
1150 }
1151 }
1152 break;
1153 }
1154
1155 case QgsColorRampShader::Type::Exact:
1156 {
1157 rat->appendField( QStringLiteral( "Value" ), Qgis::RasterAttributeTableFieldUsage::MinMax, QVariant::Type::Double );
1158 rat->appendField( QStringLiteral( "Class" ), Qgis::RasterAttributeTableFieldUsage::Name, QVariant::Type::String );
1159 rat->appendField( QStringLiteral( "Red" ), Qgis::RasterAttributeTableFieldUsage::Red, QVariant::Type::Int );
1160 rat->appendField( QStringLiteral( "Green" ), Qgis::RasterAttributeTableFieldUsage::Green, QVariant::Type::Int );
1161 rat->appendField( QStringLiteral( "Blue" ), Qgis::RasterAttributeTableFieldUsage::Blue, QVariant::Type::Int );
1162 rat->appendField( QStringLiteral( "Alpha" ), Qgis::RasterAttributeTableFieldUsage::Alpha, QVariant::Type::Int );
1163 const QList<QgsColorRampShader::ColorRampItem> rampItems { shaderFunction->colorRampItemList() };
1164 for ( const QgsColorRampShader::ColorRampItem &rampItem : std::as_const( rampItems ) )
1165 {
1166 rat->appendRow( QVariantList() << rampItem.value << rampItem.label << 0 << 0 << 0 << 255 );
1167 rat->setColor( rat->data().length() - 1, rampItem.color );
1168 }
1169 break;
1170 }
1171 }
1172
1173 if ( bandNumber )
1174 {
1175 *bandNumber = pseudoColorRenderer->band();
1176 }
1177
1178 return rat;
1179 }
1180 else
1181 {
1182 return nullptr;
1183 }
1184 }
1185 else
1186 {
1187 return nullptr;
1188 }
1189 }
1190 else
1191 {
1192 return nullptr;
1193 }
1194}
1195
1196QHash<Qgis::RasterAttributeTableFieldUsage, QgsRasterAttributeTable::UsageInformation> QgsRasterAttributeTable::usageInformation()
1197{
1198 std::call_once( usageInformationLoaderFlag, [ ]
1199 {
1200 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 } );
1201 QgsRasterAttributeTable::sUsageInformation.insert( Qgis::RasterAttributeTableFieldUsage::PixelCount, { tr( "Histogram Pixel Count" ), true, false, false, false, true, false, QList<QVariant::Type>() << QVariant::LongLong } );
1202 QgsRasterAttributeTable::sUsageInformation.insert( Qgis::RasterAttributeTableFieldUsage::Name, { tr( "Class Name" ), false, false, false, false, true, true, QList<QVariant::Type>() << QVariant::String } );
1203 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 } );
1204 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 } );
1205 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 } );
1206 QgsRasterAttributeTable::sUsageInformation.insert( Qgis::RasterAttributeTableFieldUsage::Red, { tr( "Red Color Value (0-255)" ), true, false, true, false, true, false, QList<QVariant::Type>() << QVariant::Int } );
1207 QgsRasterAttributeTable::sUsageInformation.insert( Qgis::RasterAttributeTableFieldUsage::Green, { tr( "Green Color Value (0-255)" ), true, false, true, false, true, false, QList<QVariant::Type>() << QVariant::Int } );
1208 QgsRasterAttributeTable::sUsageInformation.insert( Qgis::RasterAttributeTableFieldUsage::Blue, { tr( "Blue Color Value (0-255)" ), true, false, true, false, true, false, QList<QVariant::Type>() << QVariant::Int } );
1209 QgsRasterAttributeTable::sUsageInformation.insert( Qgis::RasterAttributeTableFieldUsage::Alpha, { tr( "Alpha Color Value (0-255)" ), true, false, true, false, true, false, QList<QVariant::Type>() << QVariant::Int } );
1210 QgsRasterAttributeTable::sUsageInformation.insert( Qgis::RasterAttributeTableFieldUsage::RedMin, { tr( "Red Color Minimum Value (0-255)" ), true, false, false, true, true, false, QList<QVariant::Type>() << QVariant::Int } );
1211 QgsRasterAttributeTable::sUsageInformation.insert( Qgis::RasterAttributeTableFieldUsage::GreenMin, { tr( "Green Color Minimum Value (0-255)" ), true, false, false, true, true, false, QList<QVariant::Type>() << QVariant::Int } );
1212 QgsRasterAttributeTable::sUsageInformation.insert( Qgis::RasterAttributeTableFieldUsage::BlueMin, { tr( "Blue Color Minimum Value (0-255)" ), true, false, false, true, true, false, QList<QVariant::Type>() << QVariant::Int } );
1213 QgsRasterAttributeTable::sUsageInformation.insert( Qgis::RasterAttributeTableFieldUsage::AlphaMin, { tr( "Alpha Color Minimum Value (0-255)" ), true, false, false, true, true, false, QList<QVariant::Type>() << QVariant::Int } );
1214 QgsRasterAttributeTable::sUsageInformation.insert( Qgis::RasterAttributeTableFieldUsage::RedMax, { tr( "Red Color Minimum Value (0-255)" ), true, false, false, true, true, false, QList<QVariant::Type>() << QVariant::Int } );
1215 QgsRasterAttributeTable::sUsageInformation.insert( Qgis::RasterAttributeTableFieldUsage::GreenMax, { tr( "Green Color Minimum Value (0-255)" ), true, false, false, true, true, false, QList<QVariant::Type>() << QVariant::Int } );
1216 QgsRasterAttributeTable::sUsageInformation.insert( Qgis::RasterAttributeTableFieldUsage::BlueMax, { tr( "Blue Color Minimum Value (0-255)" ), true, false, false, true, true, false, QList<QVariant::Type>() << QVariant::Int } );
1217 QgsRasterAttributeTable::sUsageInformation.insert( Qgis::RasterAttributeTableFieldUsage::AlphaMax, { tr( "Alpha Color Minimum Value (0-255)" ), true, false, false, true, true, false, QList<QVariant::Type>() << QVariant::Int } );
1218 // Unsupported!!
1219 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 } );
1220 } );
1221 return QgsRasterAttributeTable::sUsageInformation;
1222}
1223
1224void QgsRasterAttributeTable::setType()
1225{
1226 const QList<Qgis::RasterAttributeTableFieldUsage> fieldUsages { usages() };
1228}
1229
1230
1232QHash<int, QgsRasterAttributeTable::UsageInformation> QgsRasterAttributeTable::usageInformationInt()
1233{
1234 QHash<int, QgsRasterAttributeTable::UsageInformation> usageInfoInt;
1235 const QHash<Qgis::RasterAttributeTableFieldUsage, QgsRasterAttributeTable::UsageInformation> usageInfo { QgsRasterAttributeTable::usageInformation() };
1236 for ( auto it = usageInfo.cbegin(); it != usageInfo.cend(); ++it )
1237 {
1238 usageInfoInt.insert( static_cast<int>( it.key() ), it.value() );
1239 }
1240 return usageInfoInt;
1241}
1243
1245{
1246 return mFilePath;
1247}
1248
1249QList<QgsRasterAttributeTable::MinMaxClass> QgsRasterAttributeTable::minMaxClasses( const int classificationColumn ) const
1250{
1251 QList<QgsRasterAttributeTable::MinMaxClass> classes;
1252 if ( !isValid() )
1253 {
1254 QgsDebugMsg( "minMaxClasses was called on an invalid RAT" );
1255 return classes;
1256 }
1257
1258 const QList<Qgis::RasterAttributeTableFieldUsage> fieldUsages { usages() };
1259
1260 if ( ! fieldUsages.contains( Qgis::RasterAttributeTableFieldUsage::MinMax ) )
1261 {
1262 QgsDebugMsg( "minMaxClasses was called on a ramp raster" );
1263 return classes;
1264 }
1265
1266 const int minMaxIndex { static_cast<int>( fieldUsages.indexOf( Qgis::RasterAttributeTableFieldUsage::MinMax ) ) };
1267
1268 Q_ASSERT( minMaxIndex >= 0 );
1269
1270 int classificationIndex = classificationColumn;
1271 if ( classificationIndex >= 0 && classificationIndex < mFields.count( ) )
1272 {
1273 const Field classificationField { mFields.at( classificationIndex ) };
1274 if ( ( classificationField.usage != Qgis::RasterAttributeTableFieldUsage::Name && classificationField.usage != Qgis::RasterAttributeTableFieldUsage::Generic ) )
1275 {
1276 QgsDebugMsg( "minMaxClasses was called with a classification column which is not suitable for classification" );
1277 return classes;
1278 }
1279 }
1280 else if ( classificationIndex == -1 ) // Special value for not-set
1281 {
1282 // Find first value or generic field
1283 if ( fieldUsages.contains( Qgis::RasterAttributeTableFieldUsage::Name ) )
1284 {
1285 classificationIndex = fieldUsages.indexOf( Qgis::RasterAttributeTableFieldUsage::Name );
1286 }
1287 else if ( fieldUsages.contains( Qgis::RasterAttributeTableFieldUsage::Generic ) )
1288 {
1289 classificationIndex = fieldUsages.indexOf( Qgis::RasterAttributeTableFieldUsage::Generic );
1290 }
1291 else
1292 {
1293 classificationIndex = minMaxIndex;
1294 }
1295 }
1296 else if ( classificationIndex >= mFields.count( ) )
1297 {
1298 QgsDebugMsg( "minMaxClasses was called with a classification column out of range" );
1299 return classes;
1300 }
1301
1302 if ( classificationIndex >= 0 )
1303 {
1304 QStringList labels;
1305 int rowIdx { 0 };
1306 for ( const QVariantList &row : std::as_const( mData ) )
1307 {
1308 const QString label { row.at( classificationIndex ).toString() };
1309 bool ok;
1310 const double value { row.at( minMaxIndex ).toDouble( &ok ) };
1311 // This should never happen, could eventually become a Q_ASSERT
1312 if ( ! ok )
1313 {
1314 QgsDebugMsg( "minMaxClasses could not convert a MinMax value to double" );
1315 return classes;
1316 }
1317 if ( labels.contains( label ) )
1318 {
1319 classes[ labels.indexOf( label ) ].minMaxValues.push_back( value );
1320 }
1321 else
1322 {
1323 labels.push_back( label );
1324 classes.push_back( { label, { value }, color( rowIdx ) } );
1325 }
1326 rowIdx++;
1327 }
1328 }
1329 return classes;
1330}
1331
1332QgsGradientColorRamp QgsRasterAttributeTable::colorRamp( QStringList &labels, const int labelColumn ) const
1333{
1334 QgsGradientColorRamp ramp{ Qt::GlobalColor::white, Qt::GlobalColor::black };
1335 const QList<Qgis::RasterAttributeTableFieldUsage> fieldUsages { usages() };
1336 const int minIdx { static_cast<int>( fieldUsages.indexOf( Qgis::RasterAttributeTableFieldUsage::Min ) ) };
1337 const int maxIdx { static_cast<int>( fieldUsages.indexOf( Qgis::RasterAttributeTableFieldUsage::Max ) ) };
1338 const bool isRange { minIdx >= 0 && maxIdx >= 0 };
1339
1340 int labelIdx { labelColumn };
1341 if ( labelColumn < 0 || labelColumn >= fields().count( ) ||
1342 ( fieldUsages.at( labelColumn ) != Qgis::RasterAttributeTableFieldUsage::Name && fieldUsages.at( labelColumn ) != Qgis::RasterAttributeTableFieldUsage::Generic ) )
1343 {
1344 labelIdx = -1;
1345 }
1346
1347 if ( ! mData.isEmpty() && ( minIdx >= 0 && maxIdx >= 0 ) )
1348 {
1350 const bool hasColorOrRamp { hasColor() || hasRamp() };
1351 {
1352
1353 const double min { minimumValue() };
1354 const double max { maximumValue() };
1355 const double range { max - min };
1356
1357 if ( range != 0 )
1358 {
1359
1360 if ( ! isnan( min ) && ! isnan( max ) )
1361 {
1362 const QList<QVariantList> dataCopy( orderedRows() );
1363
1364 QgsRasterAttributeTable orderedRat;
1365 for ( const Field &f : std::as_const( mFields ) )
1366 {
1367 orderedRat.appendField( f );
1368 }
1369 for ( const QVariantList &r : std::as_const( dataCopy ) )
1370 {
1371 orderedRat.appendRow( r );
1372 }
1373
1374 QColor lastColor { ramp.color1() };
1375
1376 if ( hasColorOrRamp )
1377 {
1378 ramp.setColor1( orderedRat.hasColor() ? orderedRat.color( 0 ) : orderedRat.ramp( 0 ).color1() );
1379 ramp.setColor2( orderedRat.hasColor() ? orderedRat.color( orderedRat.data().count() - 1 ) : orderedRat.ramp( orderedRat.data().count() - 1 ).color2() );
1380 lastColor = orderedRat.hasColor() ? orderedRat.color( 0 ) : orderedRat.ramp( 0 ).color2();
1381 }
1382
1383 auto labelFromField = [ & ]( int rowIdx ) -> QString
1384 {
1385 if ( labelIdx < 0 )
1386 {
1387 return QStringLiteral( "%L1 - %L2" ).arg( orderedRat.value( rowIdx, minIdx ).toDouble() ).arg( orderedRat.value( rowIdx, maxIdx ).toDouble() );
1388 }
1389 const QVariant val( orderedRat.value( rowIdx, labelIdx ) );
1390 bool ok { true };
1391 QString res;
1392 switch ( val.type() )
1393 {
1394 case QVariant::Type::Char:
1395 return QString( val.toChar() );
1396 case QVariant::Type::Int:
1397 res = QLocale().toString( val.toInt( &ok ) );
1398 break;
1399 case QVariant::Type::LongLong:
1400 res = QLocale().toString( val.toLongLong( &ok ) );
1401 break;
1402 case QVariant::Type::UInt:
1403 res = QLocale().toString( val.toUInt( &ok ) );
1404 break;
1405 case QVariant::Type::ULongLong:
1406 res = QLocale().toString( val.toULongLong( &ok ) );
1407 break;
1408 case QVariant::Type::Double:
1409 res = QLocale().toString( val.toDouble( &ok ), 'g' );
1410 break;
1411 case QVariant::Type::String:
1412 default:
1413 return val.toString( );
1414 }
1415 return ok ? res : val.toString();
1416 };
1417
1418 // Case 1: range classes, discrete colors
1419 // - create stops for the lower value of each class except for the first,
1420 // - use the color from the previous class
1421 if ( orderedRat.hasColor() && isRange )
1422 {
1423 labels.push_back( labelFromField( 0 ) );
1424
1425 for ( int rowIdx = 1; rowIdx < orderedRat.data().count(); ++rowIdx )
1426 {
1427 const double offset { ( orderedRat.value( rowIdx, minIdx ).toDouble( ) - min ) / range };
1428 const QColor color { orderedRat.color( rowIdx - 1 ) };
1429 stops.append( QgsGradientStop( offset, color ) );
1430 labels.push_back( labelFromField( rowIdx ) );
1431 }
1432 }
1433 // Case 2: range classes, gradients colors
1434 // Take the class bounds (average value between max of previous class and min of the next)
1435 // to avoid potential overlapping or gaps between classes.
1436 // Create stop:
1437 // - first stop at value taking the max color of the previous class
1438 // - second stop at value + epsilon taking the min color of the next class, unless colors and offset are equal
1439 else if ( orderedRat.hasRamp() && isRange )
1440 {
1441 double prevOffset { 0 };
1442 labels.push_back( labelFromField( 0 ) );
1443 for ( int rowIdx = 1; rowIdx < orderedRat.data().count(); ++rowIdx )
1444 {
1445 labels.push_back( labelFromField( rowIdx ) );
1446 const int prevRowIdx { rowIdx - 1 };
1447 const double offset { ( ( orderedRat.value( rowIdx, minIdx ).toDouble( ) + orderedRat.value( prevRowIdx, maxIdx ).toDouble( ) ) / 2.0 - min ) / range };
1448 const QgsGradientColorRamp previousRamp { orderedRat.ramp( prevRowIdx ) };
1449 stops.append( QgsGradientStop( offset, previousRamp.color2() ) );
1450
1451 const QgsGradientColorRamp currentRamp { orderedRat.ramp( rowIdx ) };
1452 // An additional stop is added if the colors are different or offsets are different by 1e-6 (offset varies from 0 to 1).
1453 if ( currentRamp.color1() != previousRamp.color2() && qgsDoubleNear( offset, prevOffset, 1e-6 ) )
1454 {
1455 stops.append( QgsGradientStop( offset + std::numeric_limits<double>::epsilon(), currentRamp.color1() ) );
1456 }
1457 prevOffset = offset;
1458 }
1459 }
1460 // Case 3: range classes but no colors at all
1461 // Take the class borders (average value between max of previous class and min of the next)
1462 // Create stop for the lower class, actually skipping the upper bound of the last class
1463 else
1464 {
1465 labels.push_back( labelFromField( 0 ) );
1466
1467 for ( int rowIdx = 1; rowIdx < orderedRat.data().count(); ++rowIdx )
1468 {
1469 const int prevRowIdx { rowIdx - 1 };
1470 const double offset { ( ( orderedRat.value( rowIdx, minIdx ).toDouble( ) + orderedRat.value( prevRowIdx, maxIdx ).toDouble( ) ) / 2.0 - min ) / range };
1471 stops.append( QgsGradientStop( offset, ramp.color( offset ) ) );
1472 labels.push_back( labelFromField( rowIdx ) );
1473 }
1474 }
1475 }
1476 }
1477 }
1478
1479 ramp.setStops( stops );
1481
1482 }
1483
1484 return ramp;
1485}
1486
1487QgsRasterRenderer *QgsRasterAttributeTable::createRenderer( QgsRasterDataProvider *provider, const int bandNumber, const int classificationColumn )
1488{
1489 if ( ! provider )
1490 {
1491 return nullptr;
1492 }
1493
1494 std::unique_ptr<QgsRasterRenderer> renderer;
1495
1497 {
1498 std::unique_ptr<QgsColorRamp> ramp;
1499 if ( ! hasColor() )
1500 {
1501 ramp.reset( new QgsRandomColorRamp() );
1502 }
1504 if ( classes.isEmpty() )
1505 return nullptr;
1506
1507 renderer = std::make_unique<QgsPalettedRasterRenderer>( provider,
1508 bandNumber,
1509 classes );
1510 }
1511 else
1512 {
1513 std::unique_ptr<QgsSingleBandPseudoColorRenderer> pseudoColorRenderer = std::make_unique<QgsSingleBandPseudoColorRenderer>( provider, bandNumber );
1514 QStringList labels;
1515 // Athematic classification is not supported, but the classificationColumn will be used for labels.
1516 // if it's not specified, try to guess it here.
1517 int labelColumn { classificationColumn };
1518 if ( labelColumn < 0 )
1519 {
1520 const QList<Qgis::RasterAttributeTableFieldUsage> fieldUsages { usages() };
1521 labelColumn = fieldUsages.indexOf( Qgis::RasterAttributeTableFieldUsage::Name );
1522 if ( labelColumn < 0 )
1523 {
1524 labelColumn = fieldUsages.indexOf( Qgis::RasterAttributeTableFieldUsage::Generic );
1525 }
1526 }
1527 QgsGradientColorRamp *ramp { colorRamp( labels, labelColumn ).clone() };
1528 pseudoColorRenderer->setClassificationMin( minimumValue() );
1529 pseudoColorRenderer->setClassificationMax( maximumValue() );
1530 // Use discrete for single colors, interpolated for ramps
1531 pseudoColorRenderer->createShader( ramp, hasRamp() ? QgsColorRampShader::Type::Interpolated : QgsColorRampShader::Type::Discrete, QgsColorRampShader::ClassificationMode::Continuous, ramp->stops().count() + 2, true );
1532 pseudoColorRenderer->shader()->setMaximumValue( maximumValue() );
1533 pseudoColorRenderer->shader()->setMinimumValue( minimumValue() );
1534 // Set labels
1535 if ( QgsColorRampShader *shaderFunction = static_cast<QgsColorRampShader *>( pseudoColorRenderer->shader()->rasterShaderFunction() ) )
1536 {
1537 shaderFunction->setMinimumValue( minimumValue() );
1538 shaderFunction->setMaximumValue( maximumValue() );
1539 const bool labelsAreUsable { ramp->count() > 2 && labels.count() == ramp->count() - 1 };
1540
1541 if ( labelsAreUsable )
1542 {
1543 QList<QgsColorRampShader::ColorRampItem> newItemList;
1544 const double range { maximumValue() - minimumValue() };
1545 int stopIdx { 0 };
1546 for ( const QString &label : std::as_const( labels ) )
1547 {
1548 if ( stopIdx >= ramp->count() - 2 )
1549 {
1550 break;
1551 }
1552 double value { minimumValue() + ramp->stops().at( stopIdx ).offset * range };
1553 QgsColorRampShader::ColorRampItem item { value, ramp->stops().at( stopIdx ).color, label };
1554 newItemList.push_back( item );
1555 stopIdx++;
1556 }
1557
1558 QgsColorRampShader::ColorRampItem item { maximumValue(), ramp->color2(), labels.last() };
1559 newItemList.push_back( item );
1560 shaderFunction->setColorRampItemList( newItemList );
1561 }
1562 }
1563 renderer.reset( pseudoColorRenderer.release() );
1564 }
1565
1566 return renderer.release();
1567}
1568
1569QList<QList<QVariant> > QgsRasterAttributeTable::orderedRows() const
1570{
1571 const QList<Qgis::RasterAttributeTableFieldUsage> fieldUsages { usages() };
1572 const int minIdx { static_cast<int>( fieldUsages.indexOf( Qgis::RasterAttributeTableFieldUsage::Min ) ) };
1573 const int maxIdx { static_cast<int>( fieldUsages.indexOf( Qgis::RasterAttributeTableFieldUsage::Max ) ) };
1574 const bool isRange { minIdx >= 0 && maxIdx >= 0 };
1575 QList<QVariantList> dataCopy( mData );
1576
1577 if ( isRange )
1578 {
1579 std::sort( dataCopy.begin(), dataCopy.end(), [ & ]( const QVariantList & first, const QVariantList & second ) -> bool
1580 {
1581 return ( first.at( maxIdx ).toDouble() + first.at( minIdx ).toDouble() ) < ( second.at( maxIdx ).toDouble() + second.at( minIdx ).toDouble() );
1582 } );
1583 }
1584 else
1585 {
1586 const int minMaxIdx { static_cast<int>( fieldUsages.indexOf( Qgis::RasterAttributeTableFieldUsage::MinMax ) ) };
1587 if ( minMaxIdx < 0 )
1588 {
1589 return dataCopy;
1590 }
1591 else
1592 {
1593 std::sort( dataCopy.begin(), dataCopy.end(), [ & ]( const QVariantList & first, const QVariantList & second ) -> bool
1594 {
1595 return first.at( minMaxIdx ).toDouble() < second.at( minMaxIdx ).toDouble();
1596 } );
1597 }
1598 }
1599
1600 return dataCopy;
1601}
1602
1604{
1606}
1607
1609{
1611}
RasterAttributeTableType
The RasterAttributeTableType enum represents the type of RAT.
Definition: qgis.h:954
RasterAttributeTableFieldUsage
Flags which control behavior of raster renderers.
Definition: qgis.h:925
@ 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:52
QString name
Definition: qgsfield.h:61
QVariant::Type type
Definition: qgsfield.h:59
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:81
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:3509
QList< QgsGradientStop > QgsGradientStopsList
List of gradient stops.
QList< QgsFeature > QgsFeatureList
Definition: qgsfeature.h:920
const QgsField & field
Definition: qgsfield.h:501
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
Setting options for creating vector data providers.
Properties of a single value class.