QGIS API Documentation 3.40.0-Bratislava (b56115d8743)
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#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
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 = QgsVariantUtils::createNullVariant( field.type );
307 // Set default values
308 switch ( field.type )
309 {
310 case QMetaType::Type::QChar:
311 case QMetaType::Type::Int:
312 case QMetaType::Type::UInt:
313 case QMetaType::Type::LongLong:
314 case QMetaType::Type::ULongLong:
315 case QMetaType::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 QMetaType::Type type, QString *errorMessage )
341{
342 return insertField( position, { name, usage, type}, errorMessage );
343}
344
345bool QgsRasterAttributeTable::insertField( int position, const QString &name, const Qgis::RasterAttributeTableFieldUsage usage, const QVariant::Type type, QString *errorMessage )
346{
347 return insertField( position, name, usage, QgsVariantUtils::variantTypeToMetaType( type ), errorMessage );
348}
349
350bool QgsRasterAttributeTable::insertColor( int position, QString *errorMessage )
351{
353 int idx { position };
354 for ( const Qgis::RasterAttributeTableFieldUsage usage : std::as_const( colors ) )
355 {
356 if ( ! insertField( idx, usageName( usage ), usage, QMetaType::Type::Int, errorMessage ) )
357 {
358 return false;
359 }
360 ++idx;
361 }
362 return true;
363}
364
366{
367 if ( fieldIndex < 0 || fieldIndex >= fields().count( ) )
368 {
369 return false;
370 }
371
372 const Field field { fields().at( fieldIndex ) };
373 if ( ! usageInformation()[ usage ].allowedTypes.contains( field.type ) )
374 {
375 return false;
376 }
377
378 mFields[ fieldIndex ].usage = usage;
379 setType();
380
381 return true;
382}
383
384bool QgsRasterAttributeTable::insertRamp( int position, QString *errorMessage )
385{
387 {
388 if ( errorMessage )
389 {
390 *errorMessage = tr( "A color ramp can only be added to an athematic attribute table." );
391 }
392 }
394 int idx { position };
395 for ( const Qgis::RasterAttributeTableFieldUsage usage : std::as_const( colors ) )
396 {
397 if ( ! insertField( idx, usageName( usage ), usage, QMetaType::Type::Int, errorMessage ) )
398 {
399 return false;
400 }
401 ++idx;
402 }
403 return true;
404}
405
406bool QgsRasterAttributeTable::appendField( const QString &name, const Qgis::RasterAttributeTableFieldUsage usage, const QMetaType::Type type, QString *errorMessage )
407{
408 return insertField( mFields.count(), name, usage, type, errorMessage );
409}
410
411bool QgsRasterAttributeTable::appendField( const QString &name, const Qgis::RasterAttributeTableFieldUsage usage, const QVariant::Type type, QString *errorMessage )
412{
413 return appendField( name, usage, QgsVariantUtils::variantTypeToMetaType( type ), errorMessage );
414}
415
416bool QgsRasterAttributeTable::appendField( const Field &field, QString *errorMessage )
417{
418 return insertField( mFields.count(), field, errorMessage );
419}
420
421bool QgsRasterAttributeTable::removeField( const QString &name, QString *errorMessage )
422{
423 const auto toRemove { std::find_if( mFields.begin(), mFields.end(), [ &name ]( Field & f ) -> bool {
424 return f.name == name;
425 } )};
426
427 if ( toRemove != mFields.end() )
428 {
429 const int idx { static_cast<int>( std::distance( mFields.begin(), toRemove ) ) };
430 mFields.erase( toRemove );
431 for ( auto it = mData.begin(); it != mData.end(); ++it )
432 {
433 it->removeAt( idx );
434 }
435 setType();
436 setDirty( true );
437 return true;
438 }
439
440 if ( errorMessage )
441 {
442 *errorMessage = tr( "A field with name '%1' was not found." ).arg( name );
443 }
444 return false;
445}
446
447bool QgsRasterAttributeTable::insertRow( int position, const QVariantList &rowData, QString *errorMessage )
448{
449
450 const int realPos { std::clamp( position, 0, static_cast<int>( mData.count() ) ) };
451
452 if ( rowData.size() != mFields.size() )
453 {
454 if ( errorMessage )
455 {
456 *errorMessage = tr( "Row element count differs from field count (%1)." ).arg( mFields.size() );
457 }
458 return false;
459 }
460
461 QVariantList dataValid;
462
463 for ( int idx = 0; idx < mFields.count(); ++idx )
464 {
465 QVariant cell( rowData[ idx ] );
466 if ( ! cell.canConvert( mFields.at( idx ).type ) || ! cell.convert( mFields.at( idx ).type ) )
467 {
468 if ( errorMessage )
469 {
470 *errorMessage = tr( "Row data at column %1 cannot be converted to field type (%2)." ).arg( idx ).arg( QVariant::typeToName( mFields.at( idx ).type ) );
471 }
472 return false;
473 }
474 else
475 {
476 dataValid.append( cell );
477 }
478 }
479
480 mData.insert( realPos, dataValid );
481 setDirty( true );
482 return true;
483}
484
485bool QgsRasterAttributeTable::removeRow( int position, QString *errorMessage )
486{
487 if ( position >= mData.count() || position < 0 || mData.isEmpty() )
488 {
489 if ( errorMessage )
490 {
491 *errorMessage = tr( "Position is not valid or the table is empty." );
492 }
493 return false;
494 }
495 mData.removeAt( position );
496 setDirty( true );
497 return true;
498}
499
500bool QgsRasterAttributeTable::appendRow( const QVariantList &data, QString *errorMessage )
501{
502 return insertRow( mData.count(), data, errorMessage );
503}
504
505bool QgsRasterAttributeTable::writeToFile( const QString &path, QString *errorMessage )
506{
509 options.driverName = QStringLiteral( "ESRI Shapefile" );
510 options.fileEncoding = QStringLiteral( "UTF-8" );
511 options.layerOptions = QStringList() << QStringLiteral( "SHPT=NULL" );
512
513 std::unique_ptr<QgsVectorFileWriter> writer;
514
515 // Strip .dbf from path because OGR adds it back
516 QString cleanedPath { path };
517 if ( path.endsWith( QStringLiteral( ".dbf" ), Qt::CaseSensitivity::CaseInsensitive ) )
518 {
519 cleanedPath.chop( 4 );
520 }
521
522 cleanedPath = QgsFileUtils::ensureFileNameHasExtension( cleanedPath, {{ QStringLiteral( ".vat" ) } } );
523
525
526 cleanedPath.append( QStringLiteral( ".dbf" ) );
527
528 const QgsVectorFileWriter::WriterError error { writer->hasError() };
530 {
531 if ( errorMessage )
532 {
533 *errorMessage = tr( "Error creating Raster Attribute Table table: %1." ).arg( writer->errorMessage() );
534 }
535 return false;
536 }
537
538 QgsFeatureList features { qgisFeatures() };
539 bool result { writer->addFeatures( features ) };
540
541 if ( ! result )
542 {
543 if ( errorMessage )
544 {
545 *errorMessage = tr( "Error creating Raster Attribute Table table: could not add rows." );
546 }
547 return false;
548 }
549
550 result = writer->flushBuffer();
551
552 if ( result )
553 {
554 mFilePath = cleanedPath;
555 setDirty( false );
556 }
557
558 return result;
559}
560
561bool QgsRasterAttributeTable::readFromFile( const QString &path, QString *errorMessage )
562{
563 QgsOgrProvider ratDbfSource { path, QgsDataProvider::ProviderOptions() };
564 if ( ! ratDbfSource.isValid() )
565 {
566 if ( errorMessage )
567 {
568 *errorMessage = tr( "Error reading Raster Attribute Table table from file: invalid layer." );
569 }
570 return false;
571 }
572
573 QList<Field> oldFields = mFields;
574 QList<QVariantList> oldData = mData;
575
576 mFields.clear();
577 mData.clear();
578
579 bool hasValueField { false };
580 for ( const QgsField &field : ratDbfSource.fields() )
581 {
582 const Qgis::RasterAttributeTableFieldUsage usage { guessFieldUsage( field.name(), field.type() ) };
583 QMetaType::Type type { field.type() };
584 // DBF sets all int fields to long but for RGBA it doesn't make sense
585 if ( type == QMetaType::Type::LongLong &&
590 {
591 type = QMetaType::Type::Int;
592 }
593
595 {
596 hasValueField = true;
597 }
598
599 QgsRasterAttributeTable::Field ratField { field.name(), usage, type };
600 if ( ! appendField( ratField, errorMessage ) )
601 {
602 mFields = oldFields;
603 mData = oldData;
604 return false;
605 }
606 }
607
608 // Do we have a value field? If not, try to guess one
609 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 ) )
610 {
612 }
613
614 const int fieldCount { static_cast<int>( ratDbfSource.fields().count( ) ) };
615 QgsFeature f;
616 QgsFeatureIterator fit { ratDbfSource.getFeatures( QgsFeatureRequest() ) };
617 while ( fit.nextFeature( f ) )
618 {
619 if ( f.attributeCount() != fieldCount )
620 {
621 if ( errorMessage )
622 {
623 *errorMessage = tr( "Error reading Raster Attribute Table table from file: number of fields and number of attributes do not match." );
624 }
625 mFields = oldFields;
626 mData = oldData;
627 return false;
628 }
629 appendRow( f.attributes().toList() );
630 }
631
632 mFilePath = path;
633 setDirty( false );
634
635 return true;
636}
637
638
639bool QgsRasterAttributeTable::isValid( QString *errorMessage ) const
640{
641 QStringList errors;
642
643 if ( mFields.isEmpty() )
644 {
645 errors.push_back( tr( "The attribute table has no fields." ) );
646 }
647
648 if ( mData.isEmpty() )
649 {
650 errors.push_back( tr( "The attribute table has no rows." ) );
651 }
652
653 const QList<Qgis::RasterAttributeTableFieldUsage> fieldUsages { usages() };
654 const bool isMinMax { fieldUsages.contains( Qgis::RasterAttributeTableFieldUsage::MinMax ) };
655 const bool isValueRamp { fieldUsages.contains( Qgis::RasterAttributeTableFieldUsage::Min ) &&fieldUsages.contains( Qgis::RasterAttributeTableFieldUsage::Max ) };
656 if ( ! isMinMax && ! isValueRamp )
657 {
658 errors.push_back( tr( "The attribute table has no MinMax nor a pair of Min and Max fields." ) );
659 }
660
661 // Check color
662 if ( fieldUsages.contains( Qgis::RasterAttributeTableFieldUsage::Red ) || fieldUsages.contains( Qgis::RasterAttributeTableFieldUsage::Green ) || fieldUsages.contains( Qgis::RasterAttributeTableFieldUsage::Blue ) )
663 {
664 if ( !( fieldUsages.contains( Qgis::RasterAttributeTableFieldUsage::Red ) && fieldUsages.contains( Qgis::RasterAttributeTableFieldUsage::Green ) && fieldUsages.contains( Qgis::RasterAttributeTableFieldUsage::Blue ) ) )
665 {
666 errors.push_back( tr( "The attribute table has some but not all the fields required for color definition (Red, Green, Blue)." ) );
667 }
668 }
669
670 // Check ramp
672 {
673 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 ) ) )
674 {
675 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)." ) );
676 }
677 else if ( ! isValueRamp )
678 {
679 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." ) );
680 }
681 }
682
683 if ( errorMessage && ! errors.isEmpty() )
684 {
685 *errorMessage = errors.join( QChar( '\n' ) );
686 }
687
688 return errors.isEmpty();
689}
690
691const QList<QList<QVariant> > QgsRasterAttributeTable::data() const
692{
693 return mData;
694}
695
697{
698 for ( const Field &f : std::as_const( mFields ) )
699 {
700 if ( f.name == name )
701 {
702 if ( ok )
703 {
704 *ok = true;
705 }
706 return f;
707 }
708 }
709 if ( ok )
710 {
711 *ok = false;
712 }
713 return Field( QString(), Qgis::RasterAttributeTableFieldUsage::Generic, QMetaType::Type::QString );
714}
715
716const QList<QgsRasterAttributeTable::Field> QgsRasterAttributeTable::fieldsByUsage( const Qgis::RasterAttributeTableFieldUsage fieldUsage ) const
717{
718 QList<QgsRasterAttributeTable::Field> result;
719 for ( const Field &f : std::as_const( mFields ) )
720 {
721 if ( f.usage == fieldUsage )
722 {
723 result.push_back( f );
724 }
725 }
726 return result;
727}
728
729bool QgsRasterAttributeTable::setValue( const int row, const int column, const QVariant &value )
730{
731 if ( row < 0 || row >= mData.count( ) || column < 0 || column >= mData[ row ].count( ) )
732 {
733 return false;
734 }
735
736 QVariant newVal = value;
737 if ( column >= mFields.length() || ! value.canConvert( mFields.at( column ).type ) || ! newVal.convert( mFields.at( column ).type ) )
738 {
739 return false;
740 }
741
742 const QVariant oldVal = mData[ row ][ column ];
743
744 if ( newVal != oldVal )
745 {
746 mData[ row ][ column ] = newVal;
747 setDirty( true );
748 }
749
750 return true;
751}
752
753QVariant QgsRasterAttributeTable::value( const int row, const int column ) const
754{
755 if ( row < 0 || row >= mData.count( ) || column < 0 || column >= mData[ row ].count( ) )
756 {
757 return QVariant();
758 }
759 return mData[ row ][ column ];
760}
761
763{
764 const QList<Qgis::RasterAttributeTableFieldUsage> fieldUsages { usages() };
765 bool ok { false };
766 int fieldIdx { -1 };
767
768 if ( fieldUsages.contains( Qgis::RasterAttributeTableFieldUsage::MinMax ) )
769 {
770 fieldIdx = fieldUsages.indexOf( Qgis::RasterAttributeTableFieldUsage::MinMax );
771 }
772 else if ( fieldUsages.contains( Qgis::RasterAttributeTableFieldUsage::Min ) )
773 {
774 fieldIdx = fieldUsages.indexOf( Qgis::RasterAttributeTableFieldUsage::Min );
775 }
776
777 double min { std::numeric_limits<double>::max() };
778 for ( int rowIdx = 0; rowIdx < mData.count(); ++rowIdx )
779 {
780 min = std::min( min, value( rowIdx, fieldIdx ).toDouble( &ok ) );
781 if ( ! ok )
782 {
783 return std::numeric_limits<double>::quiet_NaN();
784 }
785 }
786
787 if ( fieldIdx == -1 || ! ok )
788 {
789 return std::numeric_limits<double>::quiet_NaN();
790 }
791 else
792 {
793 return min;
794 }
795}
796
798{
799 const QList<Qgis::RasterAttributeTableFieldUsage> fieldUsages { usages() };
800 bool ok { false };
801 int fieldIdx { -1 };
802
803 if ( fieldUsages.contains( Qgis::RasterAttributeTableFieldUsage::MinMax ) )
804 {
805 fieldIdx = fieldUsages.indexOf( Qgis::RasterAttributeTableFieldUsage::MinMax );
806 }
807 else if ( fieldUsages.contains( Qgis::RasterAttributeTableFieldUsage::Max ) )
808 {
809 fieldIdx = fieldUsages.indexOf( Qgis::RasterAttributeTableFieldUsage::Max );
810 }
811
812 double max { std::numeric_limits<double>::lowest() };
813 for ( int rowIdx = 0; rowIdx < mData.count(); ++rowIdx )
814 {
815 max = std::max( max, value( rowIdx, fieldIdx ).toDouble( &ok ) );
816 if ( ! ok )
817 {
818 return std::numeric_limits<double>::quiet_NaN();
819 }
820 }
821
822 if ( fieldIdx == -1 || ! ok )
823 {
824 return std::numeric_limits<double>::quiet_NaN();
825 }
826 else
827 {
828 return max;
829 }
830}
831
832QVariantList QgsRasterAttributeTable::row( const double matchValue ) const
833{
834 if ( ! isValid() )
835 {
836 return QVariantList();
837 }
838
839 const QList<Qgis::RasterAttributeTableFieldUsage> fieldUsages { usages() };
840
841 if ( fieldUsages.contains( Qgis::RasterAttributeTableFieldUsage::MinMax ) )
842 {
843 const int colIdx { static_cast<int>( fieldUsages.indexOf( Qgis::RasterAttributeTableFieldUsage::MinMax ) ) };
844 for ( int rowIdx = 0; rowIdx < mData.count(); ++rowIdx )
845 {
846 bool ok;
847 if ( matchValue == value( rowIdx, colIdx ).toDouble( &ok ) && ok )
848 {
849 return mData.at( rowIdx );
850 }
851 }
852 }
853 else
854 {
855 const int minColIdx { static_cast<int>( fieldUsages.indexOf( Qgis::RasterAttributeTableFieldUsage::Min ) ) };
856 const int maxColIdx { static_cast<int>( fieldUsages.indexOf( Qgis::RasterAttributeTableFieldUsage::Max ) ) };
857 for ( int rowIdx = 0; rowIdx < mData.count(); ++rowIdx )
858 {
859 bool ok;
860 if ( matchValue >= value( rowIdx, minColIdx ).toDouble( &ok ) && ok )
861 {
862 if ( matchValue < value( rowIdx, maxColIdx ).toDouble( &ok ) && ok )
863 {
864 return mData.at( rowIdx );
865 }
866 }
867 }
868 }
869 return QVariantList();
870}
871
873{
874 static const QStringList minValueNames { {
875 QStringLiteral( "min" ),
876 QStringLiteral( "min_value" ),
877 QStringLiteral( "min value" ),
878 QStringLiteral( "value min" ),
879 QStringLiteral( "value_min" ),
880 } };
881
882 static const QStringList maxValueNames { {
883 QStringLiteral( "max" ),
884 QStringLiteral( "max_value" ),
885 QStringLiteral( "max value" ),
886 QStringLiteral( "value max" ),
887 QStringLiteral( "value_max" ),
888 } };
889
890 const QString fieldLower { name.toLower() };
891
892 if ( type == QMetaType::Type::Double || type == QMetaType::Type::Int || type == QMetaType::Type::UInt || type == QMetaType::Type::LongLong || type == QMetaType::Type::ULongLong )
893 {
894 if ( minValueNames.contains( fieldLower ) )
895 {
897 }
898 else if ( maxValueNames.contains( fieldLower ) )
899 {
901 }
902 else if ( fieldLower == QLatin1String( "value" ) )
903 {
905 }
906 else if ( fieldLower == QLatin1String( "count" ) )
907 {
908 // This could really be max count but it's more likely pixel count
910 }
911 // Colors (not double)
912 else if ( type != QMetaType::Type::Double )
913 {
914 if ( fieldLower.contains( "red" ) || fieldLower == QLatin1String( "r" ) )
915 {
916 if ( fieldLower.contains( "min" ) )
917 {
919 }
920 else if ( fieldLower.contains( "max" ) )
921 {
923 }
924 else
925 {
927 }
928 }
929 else if ( fieldLower.contains( "green" ) || fieldLower == QLatin1String( "g" ) )
930 {
931 if ( fieldLower.contains( "min" ) )
932 {
934 }
935 else if ( fieldLower.contains( "max" ) )
936 {
938 }
939 else
940 {
942 }
943 }
944 else if ( fieldLower.contains( "blue" ) || fieldLower == QLatin1String( "b" ) )
945 {
946 if ( fieldLower.contains( "min" ) )
947 {
949 }
950 else if ( fieldLower.contains( "max" ) )
951 {
953 }
954 else
955 {
957 }
958 }
959 else if ( fieldLower.contains( "alpha" ) || fieldLower == QLatin1String( "a" ) )
960 {
961 if ( fieldLower.contains( "min" ) )
962 {
964 }
965 else if ( fieldLower.contains( "max" ) )
966 {
968 }
969 else
970 {
972 }
973 }
974 }
975 // end colors
976 }
977
978 if ( type == QMetaType::Type::QString ) // default to name for strings
979 {
981 }
982
983 // default to generic for all other cases
985
986}
987
992
994{
995 switch ( usage )
996 {
998 return tr( "Red" );
1000 return tr( "Green" );
1002 return tr( "Blue" );
1004 return tr( "Alpha" );
1006 return tr( "Red Minimum" );
1008 return tr( "Green Minimum" );
1010 return tr( "Blue Minimum" );
1012 return tr( "Alpha Minimum" );
1014 return tr( "Red Maximum" );
1016 return tr( "Green Maximum" );
1018 return tr( "Blue Maximum" );
1020 return tr( "Alpha Maximum" );
1022 return tr( "Generic" );
1024 return tr( "Name" );
1026 return tr( "Pixel Count" );
1028 return tr( "Maximum Count" );
1030 return tr( "Value" );
1032 return tr( "Minimum Value" );
1034 return tr( "Maximum Value" );
1035 }
1036 return QString();
1037}
1038
1060
1062{
1063
1064 if ( ! raster || ! raster->dataProvider() || ! raster->isValid() )
1065 {
1066 return nullptr;
1067 }
1068
1069 const QgsRasterRenderer *renderer = raster->renderer();
1070
1071 if ( ! renderer )
1072 {
1073 return nullptr;
1074 }
1075
1076 if ( const QgsPalettedRasterRenderer *palettedRenderer = dynamic_cast<const QgsPalettedRasterRenderer *>( renderer ) )
1077 {
1079 rat->appendField( QStringLiteral( "Value" ), Qgis::RasterAttributeTableFieldUsage::MinMax, QMetaType::Type::Double );
1080 rat->appendField( QStringLiteral( "Class" ), Qgis::RasterAttributeTableFieldUsage::Name, QMetaType::Type::QString );
1081 rat->appendField( QStringLiteral( "Red" ), Qgis::RasterAttributeTableFieldUsage::Red, QMetaType::Type::Int );
1082 rat->appendField( QStringLiteral( "Green" ), Qgis::RasterAttributeTableFieldUsage::Green, QMetaType::Type::Int );
1083 rat->appendField( QStringLiteral( "Blue" ), Qgis::RasterAttributeTableFieldUsage::Blue, QMetaType::Type::Int );
1084 rat->appendField( QStringLiteral( "Alpha" ), Qgis::RasterAttributeTableFieldUsage::Alpha, QMetaType::Type::Int );
1085
1086 const QgsPalettedRasterRenderer::ClassData classes { palettedRenderer->classes() };
1087
1088 for ( const QgsPalettedRasterRenderer::Class &klass : std::as_const( classes ) )
1089 {
1090 rat->appendRow( QVariantList() << klass.value << klass.label << 0 << 0 << 0 << 255 );
1091 rat->setColor( rat->data().length() - 1, klass.color );
1092 }
1093
1094 if ( bandNumber )
1095 {
1096 *bandNumber = palettedRenderer->inputBand();
1097 }
1098 return rat;
1099 }
1100 else if ( const QgsSingleBandPseudoColorRenderer *pseudoColorRenderer = dynamic_cast<const QgsSingleBandPseudoColorRenderer *>( renderer ) )
1101 {
1102 if ( const QgsRasterShader *shader = pseudoColorRenderer->shader() )
1103 {
1104 if ( const QgsColorRampShader *shaderFunction = dynamic_cast<const QgsColorRampShader *>( shader->rasterShaderFunction() ) )
1105 {
1107 switch ( shaderFunction->colorRampType() )
1108 {
1109
1111 {
1112 rat->appendField( QStringLiteral( "Min" ), Qgis::RasterAttributeTableFieldUsage::Min, QMetaType::Type::Double );
1113 rat->appendField( QStringLiteral( "Max" ), Qgis::RasterAttributeTableFieldUsage::Max, QMetaType::Type::Double );
1114 rat->appendField( QStringLiteral( "Class" ), Qgis::RasterAttributeTableFieldUsage::Name, QMetaType::Type::QString );
1115 rat->appendField( QStringLiteral( "RedMin" ), Qgis::RasterAttributeTableFieldUsage::RedMin, QMetaType::Type::Int );
1116 rat->appendField( QStringLiteral( "GreenMin" ), Qgis::RasterAttributeTableFieldUsage::GreenMin, QMetaType::Type::Int );
1117 rat->appendField( QStringLiteral( "BlueMin" ), Qgis::RasterAttributeTableFieldUsage::BlueMin, QMetaType::Type::Int );
1118 rat->appendField( QStringLiteral( "AlphaMin" ), Qgis::RasterAttributeTableFieldUsage::AlphaMin, QMetaType::Type::Int );
1119 rat->appendField( QStringLiteral( "RedMax" ), Qgis::RasterAttributeTableFieldUsage::RedMax, QMetaType::Type::Int );
1120 rat->appendField( QStringLiteral( "GreenMax" ), Qgis::RasterAttributeTableFieldUsage::GreenMax, QMetaType::Type::Int );
1121 rat->appendField( QStringLiteral( "BlueMax" ), Qgis::RasterAttributeTableFieldUsage::BlueMax, QMetaType::Type::Int );
1122 rat->appendField( QStringLiteral( "AlphaMax" ), Qgis::RasterAttributeTableFieldUsage::AlphaMax, QMetaType::Type::Int );
1123 const QList<QgsColorRampShader::ColorRampItem> rampItems { shaderFunction->colorRampItemList() };
1124 if ( rampItems.size() > 1 )
1125 {
1126 QColor color1 { rampItems.at( 0 ).color };
1127 QString label1 { rampItems.at( 0 ).label };
1128 QVariant value1( rampItems.at( 0 ).value );
1129 const int rampItemSize = rampItems.size();
1130 for ( int i = 1; i < rampItemSize; ++i )
1131 {
1132 const QgsColorRampShader::ColorRampItem &rampItem { rampItems.at( i )};
1133 rat->appendRow( QVariantList() << value1 << rampItem.value << QStringLiteral( "%1 - %2" ).arg( label1, rampItem.label ) << 0 << 0 << 0 << 255 << 0 << 0 << 0 << 255 );
1134 rat->setRamp( rat->data().length() - 1, color1, rampItem.color );
1135 label1 = rampItem.label;
1136 value1 = rampItem.value;
1137 color1 = rampItem.color;
1138 }
1139 }
1140 break;
1141 }
1142
1144 {
1145 rat->appendField( QStringLiteral( "Min" ), Qgis::RasterAttributeTableFieldUsage::Min, QMetaType::Type::Double );
1146 rat->appendField( QStringLiteral( "Max" ), Qgis::RasterAttributeTableFieldUsage::Max, QMetaType::Type::Double );
1147 rat->appendField( QStringLiteral( "Class" ), Qgis::RasterAttributeTableFieldUsage::Name, QMetaType::Type::QString );
1148 rat->appendField( QStringLiteral( "Red" ), Qgis::RasterAttributeTableFieldUsage::Red, QMetaType::Type::Int );
1149 rat->appendField( QStringLiteral( "Green" ), Qgis::RasterAttributeTableFieldUsage::Green, QMetaType::Type::Int );
1150 rat->appendField( QStringLiteral( "Blue" ), Qgis::RasterAttributeTableFieldUsage::Blue, QMetaType::Type::Int );
1151 rat->appendField( QStringLiteral( "Alpha" ), Qgis::RasterAttributeTableFieldUsage::Alpha, QMetaType::Type::Int );
1152 const QList<QgsColorRampShader::ColorRampItem> rampItems { shaderFunction->colorRampItemList() };
1153 if ( rampItems.size( ) > 1 )
1154 {
1155 QColor color1 { rampItems.at( 0 ).color };
1156 QString label1 { rampItems.at( 0 ).label };
1157 QVariant value1( rampItems.at( 0 ).value );
1158 const int rampItemSize = rampItems.size();
1159 for ( int i = 1; i < rampItemSize; ++i )
1160 {
1161 const QgsColorRampShader::ColorRampItem &rampItem { rampItems.at( i )};
1162 rat->appendRow( QVariantList() << value1 << rampItem.value << QStringLiteral( "%1 - %2" ).arg( label1, rampItem.label ) << 0 << 0 << 0 << 255 << 0 << 0 << 0 << 255 );
1163 rat->setRamp( rat->data().length() - 1, color1, rampItem.color );
1164 label1 = rampItem.label;
1165 value1 = rampItem.value;
1166 color1 = rampItem.color;
1167 }
1168 }
1169 break;
1170 }
1171
1173 {
1174 rat->appendField( QStringLiteral( "Value" ), Qgis::RasterAttributeTableFieldUsage::MinMax, QMetaType::Type::Double );
1175 rat->appendField( QStringLiteral( "Class" ), Qgis::RasterAttributeTableFieldUsage::Name, QMetaType::Type::QString );
1176 rat->appendField( QStringLiteral( "Red" ), Qgis::RasterAttributeTableFieldUsage::Red, QMetaType::Type::Int );
1177 rat->appendField( QStringLiteral( "Green" ), Qgis::RasterAttributeTableFieldUsage::Green, QMetaType::Type::Int );
1178 rat->appendField( QStringLiteral( "Blue" ), Qgis::RasterAttributeTableFieldUsage::Blue, QMetaType::Type::Int );
1179 rat->appendField( QStringLiteral( "Alpha" ), Qgis::RasterAttributeTableFieldUsage::Alpha, QMetaType::Type::Int );
1180 const QList<QgsColorRampShader::ColorRampItem> rampItems { shaderFunction->colorRampItemList() };
1181 for ( const QgsColorRampShader::ColorRampItem &rampItem : std::as_const( rampItems ) )
1182 {
1183 rat->appendRow( QVariantList() << rampItem.value << rampItem.label << 0 << 0 << 0 << 255 );
1184 rat->setColor( rat->data().length() - 1, rampItem.color );
1185 }
1186 break;
1187 }
1188 }
1189
1190 if ( bandNumber )
1191 {
1192 *bandNumber = pseudoColorRenderer->inputBand();
1193 }
1194
1195 return rat;
1196 }
1197 else
1198 {
1199 return nullptr;
1200 }
1201 }
1202 else
1203 {
1204 return nullptr;
1205 }
1206 }
1207 else
1208 {
1209 return nullptr;
1210 }
1211}
1212
1213QHash<Qgis::RasterAttributeTableFieldUsage, QgsRasterAttributeTable::UsageInformation> QgsRasterAttributeTable::usageInformation()
1214{
1215 std::call_once( usageInformationLoaderFlag, [ ]
1216 {
1217 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 } );
1218 QgsRasterAttributeTable::sUsageInformation.insert( Qgis::RasterAttributeTableFieldUsage::PixelCount, { tr( "Histogram Pixel Count" ), true, false, false, false, true, false, QList<QMetaType::Type>() << QMetaType::Type::LongLong } );
1219 QgsRasterAttributeTable::sUsageInformation.insert( Qgis::RasterAttributeTableFieldUsage::Name, { tr( "Class Name" ), false, false, false, false, true, true, QList<QMetaType::Type>() << QMetaType::Type::QString } );
1220 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 } );
1221 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 } );
1222 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 } );
1223 QgsRasterAttributeTable::sUsageInformation.insert( Qgis::RasterAttributeTableFieldUsage::Red, { tr( "Red Color Value (0-255)" ), true, false, true, false, true, false, QList<QMetaType::Type>() << QMetaType::Type::Int } );
1224 QgsRasterAttributeTable::sUsageInformation.insert( Qgis::RasterAttributeTableFieldUsage::Green, { tr( "Green Color Value (0-255)" ), true, false, true, false, true, false, QList<QMetaType::Type>() << QMetaType::Type::Int } );
1225 QgsRasterAttributeTable::sUsageInformation.insert( Qgis::RasterAttributeTableFieldUsage::Blue, { tr( "Blue Color Value (0-255)" ), true, false, true, false, true, false, QList<QMetaType::Type>() << QMetaType::Type::Int } );
1226 QgsRasterAttributeTable::sUsageInformation.insert( Qgis::RasterAttributeTableFieldUsage::Alpha, { tr( "Alpha Color Value (0-255)" ), true, false, true, false, true, false, QList<QMetaType::Type>() << QMetaType::Type::Int } );
1227 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 } );
1228 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 } );
1229 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 } );
1230 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 } );
1231 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 } );
1232 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 } );
1233 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 } );
1234 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 } );
1235 // Unsupported!!
1236 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 } );
1237 } );
1238 return QgsRasterAttributeTable::sUsageInformation;
1239}
1240
1241void QgsRasterAttributeTable::setType()
1242{
1243 const QList<Qgis::RasterAttributeTableFieldUsage> fieldUsages { usages() };
1245}
1246
1247
1249QHash<int, QgsRasterAttributeTable::UsageInformation> QgsRasterAttributeTable::usageInformationInt()
1250{
1251 QHash<int, QgsRasterAttributeTable::UsageInformation> usageInfoInt;
1252 const QHash<Qgis::RasterAttributeTableFieldUsage, QgsRasterAttributeTable::UsageInformation> usageInfo { QgsRasterAttributeTable::usageInformation() };
1253 for ( auto it = usageInfo.cbegin(); it != usageInfo.cend(); ++it )
1254 {
1255 usageInfoInt.insert( static_cast<int>( it.key() ), it.value() );
1256 }
1257 return usageInfoInt;
1258}
1260
1262{
1263 return mFilePath;
1264}
1265
1266QList<QgsRasterAttributeTable::MinMaxClass> QgsRasterAttributeTable::minMaxClasses( const int classificationColumn ) const
1267{
1268 QList<QgsRasterAttributeTable::MinMaxClass> classes;
1269 if ( !isValid() )
1270 {
1271 QgsDebugError( "minMaxClasses was called on an invalid RAT" );
1272 return classes;
1273 }
1274
1275 const QList<Qgis::RasterAttributeTableFieldUsage> fieldUsages { usages() };
1276
1277 if ( ! fieldUsages.contains( Qgis::RasterAttributeTableFieldUsage::MinMax ) )
1278 {
1279 QgsDebugError( "minMaxClasses was called on a ramp raster" );
1280 return classes;
1281 }
1282
1283 const int minMaxIndex { static_cast<int>( fieldUsages.indexOf( Qgis::RasterAttributeTableFieldUsage::MinMax ) ) };
1284
1285 Q_ASSERT( minMaxIndex >= 0 );
1286
1287 int classificationIndex = classificationColumn;
1288 if ( classificationIndex >= 0 && classificationIndex < mFields.count( ) )
1289 {
1290 const Field classificationField { mFields.at( classificationIndex ) };
1291 if ( ( classificationField.usage != Qgis::RasterAttributeTableFieldUsage::Name && classificationField.usage != Qgis::RasterAttributeTableFieldUsage::Generic ) )
1292 {
1293 QgsDebugError( "minMaxClasses was called with a classification column which is not suitable for classification" );
1294 return classes;
1295 }
1296 }
1297 else if ( classificationIndex == -1 ) // Special value for not-set
1298 {
1299 // Find first value or generic field
1300 if ( fieldUsages.contains( Qgis::RasterAttributeTableFieldUsage::Name ) )
1301 {
1302 classificationIndex = fieldUsages.indexOf( Qgis::RasterAttributeTableFieldUsage::Name );
1303 }
1304 else if ( fieldUsages.contains( Qgis::RasterAttributeTableFieldUsage::Generic ) )
1305 {
1306 classificationIndex = fieldUsages.indexOf( Qgis::RasterAttributeTableFieldUsage::Generic );
1307 }
1308 else
1309 {
1310 classificationIndex = minMaxIndex;
1311 }
1312 }
1313 else if ( classificationIndex >= mFields.count( ) )
1314 {
1315 QgsDebugError( "minMaxClasses was called with a classification column out of range" );
1316 return classes;
1317 }
1318
1319 if ( classificationIndex >= 0 )
1320 {
1321 QStringList labels;
1322 int rowIdx { 0 };
1323 for ( const QVariantList &row : std::as_const( mData ) )
1324 {
1325 const QString label { row.at( classificationIndex ).toString() };
1326 bool ok;
1327 const double value { row.at( minMaxIndex ).toDouble( &ok ) };
1328 // This should never happen, could eventually become a Q_ASSERT
1329 if ( ! ok )
1330 {
1331 QgsDebugError( "minMaxClasses could not convert a MinMax value to double" );
1332 return classes;
1333 }
1334 if ( labels.contains( label ) )
1335 {
1336 classes[ labels.indexOf( label ) ].minMaxValues.push_back( value );
1337 }
1338 else
1339 {
1340 labels.push_back( label );
1341 classes.push_back( { label, { value }, color( rowIdx ) } );
1342 }
1343 rowIdx++;
1344 }
1345 }
1346 return classes;
1347}
1348
1349QgsGradientColorRamp QgsRasterAttributeTable::colorRamp( QStringList &labels, const int labelColumn ) const
1350{
1351 QgsGradientColorRamp ramp{ Qt::GlobalColor::white, Qt::GlobalColor::black };
1352 const QList<Qgis::RasterAttributeTableFieldUsage> fieldUsages { usages() };
1353 const int minIdx { static_cast<int>( fieldUsages.indexOf( Qgis::RasterAttributeTableFieldUsage::Min ) ) };
1354 const int maxIdx { static_cast<int>( fieldUsages.indexOf( Qgis::RasterAttributeTableFieldUsage::Max ) ) };
1355 const bool isRange { minIdx >= 0 && maxIdx >= 0 };
1356
1357 int labelIdx { labelColumn };
1358 if ( labelColumn < 0 || labelColumn >= fields().count( ) ||
1359 ( fieldUsages.at( labelColumn ) != Qgis::RasterAttributeTableFieldUsage::Name && fieldUsages.at( labelColumn ) != Qgis::RasterAttributeTableFieldUsage::Generic ) )
1360 {
1361 labelIdx = -1;
1362 }
1363
1364 if ( ! mData.isEmpty() && ( minIdx >= 0 && maxIdx >= 0 ) )
1365 {
1367 const bool hasColorOrRamp { hasColor() || hasRamp() };
1368 {
1369
1370 const double min { minimumValue() };
1371 const double max { maximumValue() };
1372 const double range { max - min };
1373
1374 if ( range != 0 )
1375 {
1376
1377 if ( ! isnan( min ) && ! isnan( max ) )
1378 {
1379 const QList<QVariantList> dataCopy( orderedRows() );
1380
1381 QgsRasterAttributeTable orderedRat;
1382 for ( const Field &f : std::as_const( mFields ) )
1383 {
1384 orderedRat.appendField( f );
1385 }
1386 for ( const QVariantList &r : std::as_const( dataCopy ) )
1387 {
1388 orderedRat.appendRow( r );
1389 }
1390
1391 QColor lastColor { ramp.color1() };
1392
1393 if ( hasColorOrRamp )
1394 {
1395 ramp.setColor1( orderedRat.hasColor() ? orderedRat.color( 0 ) : orderedRat.ramp( 0 ).color1() );
1396 ramp.setColor2( orderedRat.hasColor() ? orderedRat.color( orderedRat.data().count() - 1 ) : orderedRat.ramp( orderedRat.data().count() - 1 ).color2() );
1397 lastColor = orderedRat.hasColor() ? orderedRat.color( 0 ) : orderedRat.ramp( 0 ).color2();
1398 }
1399
1400 auto labelFromField = [ & ]( int rowIdx ) -> QString
1401 {
1402 if ( labelIdx < 0 )
1403 {
1404 return QStringLiteral( "%L1 - %L2" ).arg( orderedRat.value( rowIdx, minIdx ).toDouble() ).arg( orderedRat.value( rowIdx, maxIdx ).toDouble() );
1405 }
1406 const QVariant val( orderedRat.value( rowIdx, labelIdx ) );
1407 bool ok { true };
1408 QString res;
1409 switch ( val.userType() )
1410 {
1411 case QMetaType::Type::QChar:
1412 return QString( val.toChar() );
1413 case QMetaType::Type::Int:
1414 res = QLocale().toString( val.toInt( &ok ) );
1415 break;
1416 case QMetaType::Type::LongLong:
1417 res = QLocale().toString( val.toLongLong( &ok ) );
1418 break;
1419 case QMetaType::Type::UInt:
1420 res = QLocale().toString( val.toUInt( &ok ) );
1421 break;
1422 case QMetaType::Type::ULongLong:
1423 res = QLocale().toString( val.toULongLong( &ok ) );
1424 break;
1425 case QMetaType::Type::Double:
1426 res = QLocale().toString( val.toDouble( &ok ), 'g' );
1427 break;
1428 case QMetaType::Type::QString:
1429 default:
1430 return val.toString( );
1431 }
1432 return ok ? res : val.toString();
1433 };
1434
1435 // Case 1: range classes, discrete colors
1436 // - create stops for the lower value of each class except for the first,
1437 // - use the color from the previous class
1438 if ( orderedRat.hasColor() && isRange )
1439 {
1440 labels.push_back( labelFromField( 0 ) );
1441
1442 for ( int rowIdx = 1; rowIdx < orderedRat.data().count(); ++rowIdx )
1443 {
1444 const double offset { ( orderedRat.value( rowIdx, minIdx ).toDouble( ) - min ) / range };
1445 const QColor color { orderedRat.color( rowIdx - 1 ) };
1446 stops.append( QgsGradientStop( offset, color ) );
1447 labels.push_back( labelFromField( rowIdx ) );
1448 }
1449 }
1450 // Case 2: range classes, gradients colors
1451 // Take the class bounds (average value between max of previous class and min of the next)
1452 // to avoid potential overlapping or gaps between classes.
1453 // Create stop:
1454 // - first stop at value taking the max color of the previous class
1455 // - second stop at value + epsilon taking the min color of the next class, unless colors and offset are equal
1456 else if ( orderedRat.hasRamp() && isRange )
1457 {
1458 double prevOffset { 0 };
1459 labels.push_back( labelFromField( 0 ) );
1460 for ( int rowIdx = 1; rowIdx < orderedRat.data().count(); ++rowIdx )
1461 {
1462 labels.push_back( labelFromField( rowIdx ) );
1463 const int prevRowIdx { rowIdx - 1 };
1464 const double offset { ( ( orderedRat.value( rowIdx, minIdx ).toDouble( ) + orderedRat.value( prevRowIdx, maxIdx ).toDouble( ) ) / 2.0 - min ) / range };
1465 const QgsGradientColorRamp previousRamp { orderedRat.ramp( prevRowIdx ) };
1466 stops.append( QgsGradientStop( offset, previousRamp.color2() ) );
1467
1468 const QgsGradientColorRamp currentRamp { orderedRat.ramp( rowIdx ) };
1469 // An additional stop is added if the colors are different or offsets are different by 1e-6 (offset varies from 0 to 1).
1470 if ( currentRamp.color1() != previousRamp.color2() && qgsDoubleNear( offset, prevOffset, 1e-6 ) )
1471 {
1472 stops.append( QgsGradientStop( offset + std::numeric_limits<double>::epsilon(), currentRamp.color1() ) );
1473 }
1474 prevOffset = offset;
1475 }
1476 }
1477 // Case 3: range classes but no colors at all
1478 // Take the class borders (average value between max of previous class and min of the next)
1479 // Create stop for the lower class, actually skipping the upper bound of the last class
1480 else
1481 {
1482 labels.push_back( labelFromField( 0 ) );
1483
1484 for ( int rowIdx = 1; rowIdx < orderedRat.data().count(); ++rowIdx )
1485 {
1486 const int prevRowIdx { rowIdx - 1 };
1487 const double offset { ( ( orderedRat.value( rowIdx, minIdx ).toDouble( ) + orderedRat.value( prevRowIdx, maxIdx ).toDouble( ) ) / 2.0 - min ) / range };
1488 stops.append( QgsGradientStop( offset, ramp.color( offset ) ) );
1489 labels.push_back( labelFromField( rowIdx ) );
1490 }
1491 }
1492 }
1493 }
1494 }
1495
1496 ramp.setStops( stops );
1498
1499 }
1500
1501 return ramp;
1502}
1503
1504QgsRasterRenderer *QgsRasterAttributeTable::createRenderer( QgsRasterDataProvider *provider, const int bandNumber, const int classificationColumn )
1505{
1506 if ( ! provider )
1507 {
1508 return nullptr;
1509 }
1510
1511 std::unique_ptr<QgsRasterRenderer> renderer;
1512
1514 {
1515 std::unique_ptr<QgsColorRamp> ramp;
1516 if ( ! hasColor() )
1517 {
1518 ramp.reset( new QgsRandomColorRamp() );
1519 }
1521 if ( classes.isEmpty() )
1522 return nullptr;
1523
1524 renderer = std::make_unique<QgsPalettedRasterRenderer>( provider,
1525 bandNumber,
1526 classes );
1527 }
1528 else
1529 {
1530 std::unique_ptr<QgsSingleBandPseudoColorRenderer> pseudoColorRenderer = std::make_unique<QgsSingleBandPseudoColorRenderer>( provider, bandNumber );
1531 QStringList labels;
1532 // Athematic classification is not supported, but the classificationColumn will be used for labels.
1533 // if it's not specified, try to guess it here.
1534 int labelColumn { classificationColumn };
1535 if ( labelColumn < 0 )
1536 {
1537 const QList<Qgis::RasterAttributeTableFieldUsage> fieldUsages { usages() };
1538 labelColumn = fieldUsages.indexOf( Qgis::RasterAttributeTableFieldUsage::Name );
1539 if ( labelColumn < 0 )
1540 {
1541 labelColumn = fieldUsages.indexOf( Qgis::RasterAttributeTableFieldUsage::Generic );
1542 }
1543 }
1544 QgsGradientColorRamp *ramp { colorRamp( labels, labelColumn ).clone() };
1545 pseudoColorRenderer->setClassificationMin( minimumValue() );
1546 pseudoColorRenderer->setClassificationMax( maximumValue() );
1547 // Use discrete for single colors, interpolated for ramps
1549 if ( pseudoColorRenderer->shader() )
1550 {
1551 pseudoColorRenderer->shader()->setMaximumValue( maximumValue() );
1552 pseudoColorRenderer->shader()->setMinimumValue( minimumValue() );
1553 // Set labels
1554 if ( QgsColorRampShader *shaderFunction = static_cast<QgsColorRampShader *>( pseudoColorRenderer->shader()->rasterShaderFunction() ) )
1555 {
1556 shaderFunction->setMinimumValue( minimumValue() );
1557 shaderFunction->setMaximumValue( maximumValue() );
1558 const bool labelsAreUsable { ramp->count() > 2 && labels.count() == ramp->count() - 1 };
1559
1560 if ( labelsAreUsable )
1561 {
1562 QList<QgsColorRampShader::ColorRampItem> newItemList;
1563 const double range { maximumValue() - minimumValue() };
1564 int stopIdx { 0 };
1565 for ( const QString &label : std::as_const( labels ) )
1566 {
1567 if ( stopIdx >= ramp->count() - 2 )
1568 {
1569 break;
1570 }
1571 double value { minimumValue() + ramp->stops().at( stopIdx ).offset * range };
1572 QgsColorRampShader::ColorRampItem item { value, ramp->stops().at( stopIdx ).color, label };
1573 newItemList.push_back( item );
1574 stopIdx++;
1575 }
1576
1577 QgsColorRampShader::ColorRampItem item { maximumValue(), ramp->color2(), labels.last() };
1578 newItemList.push_back( item );
1579 shaderFunction->setColorRampItemList( newItemList );
1580 }
1581 }
1582 }
1583 renderer.reset( pseudoColorRenderer.release() );
1584 }
1585
1586 return renderer.release();
1587}
1588
1589QList<QList<QVariant> > QgsRasterAttributeTable::orderedRows() const
1590{
1591 const QList<Qgis::RasterAttributeTableFieldUsage> fieldUsages { usages() };
1592 const int minIdx { static_cast<int>( fieldUsages.indexOf( Qgis::RasterAttributeTableFieldUsage::Min ) ) };
1593 const int maxIdx { static_cast<int>( fieldUsages.indexOf( Qgis::RasterAttributeTableFieldUsage::Max ) ) };
1594 const bool isRange { minIdx >= 0 && maxIdx >= 0 };
1595 QList<QVariantList> dataCopy( mData );
1596
1597 if ( isRange )
1598 {
1599 std::sort( dataCopy.begin(), dataCopy.end(), [ & ]( const QVariantList & first, const QVariantList & second ) -> bool
1600 {
1601 return ( first.at( maxIdx ).toDouble() + first.at( minIdx ).toDouble() ) < ( second.at( maxIdx ).toDouble() + second.at( minIdx ).toDouble() );
1602 } );
1603 }
1604 else
1605 {
1606 const int minMaxIdx { static_cast<int>( fieldUsages.indexOf( Qgis::RasterAttributeTableFieldUsage::MinMax ) ) };
1607 if ( minMaxIdx < 0 )
1608 {
1609 return dataCopy;
1610 }
1611 else
1612 {
1613 std::sort( dataCopy.begin(), dataCopy.end(), [ & ]( const QVariantList & first, const QVariantList & second ) -> bool
1614 {
1615 return first.at( minMaxIdx ).toDouble() < second.at( minMaxIdx ).toDouble();
1616 } );
1617 }
1618 }
1619
1620 return dataCopy;
1621}
1622
1627
@ Exact
Assigns the color of the exact matching value in the color ramp item list.
@ Linear
Interpolates the color between two class breaks linearly.
@ Discrete
Assigns the color of the higher class for every pixel between two class breaks.
RasterAttributeTableType
The RasterAttributeTableType enum represents the type of RAT.
Definition qgis.h:1463
@ Continuous
Uses breaks from color palette.
RasterAttributeTableFieldUsage
The RasterAttributeTableFieldUsage enum represents the usage of a Raster Attribute Table field.
Definition qgis.h:1434
@ 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.
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:58
QgsAttributes attributes
Definition qgsfeature.h:67
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:53
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:69
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.
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 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 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:5917
QList< QgsGradientStop > QgsGradientStopsList
List of gradient stops.
QList< QgsFeature > QgsFeatureList
#define QgsDebugError(str)
Definition qgslogger.h:38
Setting options for creating vector data providers.
Properties of a single value class.