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