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