Skip to content

Commit 4f49b8b

Browse files
committed
Merge pull request #3121 from rouault/pyramids
Various improvements and fixes related to raster pyramids
2 parents 2c1f2ce + 88c8d35 commit 4f49b8b

9 files changed

+150
-42
lines changed

src/app/qgsrasterlayerproperties.cpp

+18-1
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,16 @@ QgsRasterLayerProperties::QgsRasterLayerProperties( QgsMapLayer* lyr, QgsMapCanv
183183
{
184184
cboResamplingMethod->addItem( method.second, method.first );
185185
}
186+
187+
// keep it in sync with qgsrasterpyramidsoptionwidget.cpp
188+
QString prefix = provider->name() + "/driverOptions/_pyramids/";
189+
QSettings mySettings;
190+
QString defaultMethod = mySettings.value( prefix + "resampling", "AVERAGE" ).toString();
191+
int idx = cboResamplingMethod->findData( defaultMethod );
192+
if ( idx >= 0 )
193+
cboResamplingMethod->setCurrentIndex( idx );
194+
195+
186196
// build pyramid list
187197
QList< QgsRasterPyramid > myPyramidList = provider->buildPyramidList();
188198
QList< QgsRasterPyramid >::iterator myRasterPyramidIterator;
@@ -988,6 +998,13 @@ void QgsRasterLayerProperties::on_buttonBuildPyramids_clicked()
988998
//mark to be pyramided
989999
myPyramidList[myCounterInt].build = myItem->isSelected() || myPyramidList[myCounterInt].exists;
9901000
}
1001+
1002+
// keep it in sync with qgsrasterpyramidsoptionwidget.cpp
1003+
QString prefix = provider->name() + "/driverOptions/_pyramids/";
1004+
QSettings mySettings;
1005+
QString resamplingMethod( cboResamplingMethod->itemData( cboResamplingMethod->currentIndex() ).toString() );
1006+
mySettings.setValue( prefix + "resampling", resamplingMethod );
1007+
9911008
//
9921009
// Ask raster layer to build the pyramids
9931010
//
@@ -996,7 +1013,7 @@ void QgsRasterLayerProperties::on_buttonBuildPyramids_clicked()
9961013
QApplication::setOverrideCursor( Qt::WaitCursor );
9971014
QString res = provider->buildPyramids(
9981015
myPyramidList,
999-
cboResamplingMethod->itemData( cboResamplingMethod->currentIndex() ).toString(),
1016+
resamplingMethod,
10001017
( QgsRaster::RasterPyramidsFormat ) cbxPyramidsFormat->currentIndex() );
10011018
QApplication::restoreOverrideCursor();
10021019
mPyramidProgress->setValue( 0 );

src/gui/qgsrasterformatsaveoptionswidget.cpp

+22-6
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@
3131

3232
QMap< QString, QStringList > QgsRasterFormatSaveOptionsWidget::mBuiltinProfiles;
3333

34+
static const QString PYRAMID_JPEG_YCBCR_COMPRESSION( "JPEG_QUALITY_OVERVIEW=75 COMPRESS_OVERVIEW=JPEG PHOTOMETRIC_OVERVIEW=YCBCR INTERLEAVE_OVERVIEW=PIXEL" );
35+
static const QString PYRAMID_JPEG_COMPRESSION( "JPEG_QUALITY_OVERVIEW=75 COMPRESS_OVERVIEW=JPEG INTERLEAVE_OVERVIEW=PIXEL" );
36+
3437
QgsRasterFormatSaveOptionsWidget::QgsRasterFormatSaveOptionsWidget( QWidget* parent, const QString& format,
3538
QgsRasterFormatSaveOptionsWidget::Type type, const QString& provider )
3639
: QWidget( parent )
@@ -81,7 +84,7 @@ QgsRasterFormatSaveOptionsWidget::QgsRasterFormatSaveOptionsWidget( QWidget* par
8184
<< "COMPRESS_OVERVIEW=DEFLATE PREDICTOR_OVERVIEW=2 ZLEVEL=9" ); // how to set zlevel?
8285
mBuiltinProfiles[ "z__pyramids_gtiff_4jpeg" ] =
8386
( QStringList() << "_pyramids" << tr( "JPEG compression" )
84-
<< "JPEG_QUALITY_OVERVIEW=75 COMPRESS_OVERVIEW=JPEG PHOTOMETRIC_OVERVIEW=YCBCR INTERLEAVE_OVERVIEW=PIXEL" );
87+
<< PYRAMID_JPEG_YCBCR_COMPRESSION );
8588
}
8689

8790
connect( mProfileComboBox, SIGNAL( currentIndexChanged( const QString & ) ),
@@ -151,10 +154,15 @@ void QgsRasterFormatSaveOptionsWidget::setType( QgsRasterFormatSaveOptionsWidget
151154
}
152155
}
153156

157+
QString QgsRasterFormatSaveOptionsWidget::pseudoFormat() const
158+
{
159+
return mPyramids ? "_pyramids" : mFormat;
160+
}
161+
154162
void QgsRasterFormatSaveOptionsWidget::updateProfiles()
155163
{
156164
// build profiles list = user + builtin(last)
157-
QString format = mPyramids ? "_pyramids" : mFormat;
165+
QString format = pseudoFormat();
158166
QStringList profileKeys = profiles();
159167
QMapIterator<QString, QStringList> it( mBuiltinProfiles );
160168
while ( it.hasNext() )
@@ -209,6 +217,14 @@ void QgsRasterFormatSaveOptionsWidget::updateOptions()
209217
QString myOptions = mOptionsMap.value( currentProfileKey() );
210218
QStringList myOptionsList = myOptions.trimmed().split( ' ', QString::SkipEmptyParts );
211219

220+
// If the default JPEG compression profile was selected, remove PHOTOMETRIC_OVERVIEW=YCBCR
221+
// if the raster is not RGB. Otherwise this is bound to fail afterwards.
222+
if ( mRasterLayer && mRasterLayer->bandCount() != 3 &&
223+
myOptions == PYRAMID_JPEG_YCBCR_COMPRESSION )
224+
{
225+
myOptions = PYRAMID_JPEG_COMPRESSION;
226+
}
227+
212228
if ( mOptionsStackedWidget->currentIndex() == 0 )
213229
{
214230
mOptionsTable->setRowCount( 0 );
@@ -487,7 +503,7 @@ QString QgsRasterFormatSaveOptionsWidget::settingsKey( QString profileName ) con
487503
profileName = "/profile_" + profileName;
488504
else
489505
profileName = "/profile_default" + profileName;
490-
return mProvider + "/driverOptions/" + mFormat.toLower() + profileName + "/create";
506+
return mProvider + "/driverOptions/" + pseudoFormat().toLower() + profileName + "/create";
491507
}
492508

493509
QString QgsRasterFormatSaveOptionsWidget::currentProfileKey() const
@@ -523,9 +539,9 @@ void QgsRasterFormatSaveOptionsWidget::setCreateOptions()
523539
myProfiles += i.key() + QLatin1String( " " );
524540
++i;
525541
}
526-
mySettings.setValue( mProvider + "/driverOptions/" + mFormat.toLower() + "/profiles",
542+
mySettings.setValue( mProvider + "/driverOptions/" + pseudoFormat().toLower() + "/profiles",
527543
myProfiles.trimmed() );
528-
mySettings.setValue( mProvider + "/driverOptions/" + mFormat.toLower() + "/defaultProfile",
544+
mySettings.setValue( mProvider + "/driverOptions/" + pseudoFormat().toLower() + "/defaultProfile",
529545
currentProfileKey().trimmed() );
530546
}
531547

@@ -543,7 +559,7 @@ void QgsRasterFormatSaveOptionsWidget::setCreateOptions( const QString& profileN
543559
QStringList QgsRasterFormatSaveOptionsWidget::profiles() const
544560
{
545561
QSettings mySettings;
546-
return mySettings.value( mProvider + "/driverOptions/" + mFormat.toLower() + "/profiles", "" ).toString().trimmed().split( ' ', QString::SkipEmptyParts );
562+
return mySettings.value( mProvider + "/driverOptions/" + pseudoFormat().toLower() + "/profiles", "" ).toString().trimmed().split( ' ', QString::SkipEmptyParts );
547563
}
548564

549565
void QgsRasterFormatSaveOptionsWidget::swapOptionsUI( int newIndex )

src/gui/qgsrasterformatsaveoptionswidget.h

+1
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ class GUI_EXPORT QgsRasterFormatSaveOptionsWidget: public QWidget,
104104
void setCreateOptions( const QString& profile, const QStringList& list );
105105
QStringList profiles() const;
106106
bool eventFilter( QObject *obj, QEvent *event ) override;
107+
QString pseudoFormat() const;
107108

108109
};
109110

src/gui/qgsrasterlayersaveasdialog.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
#include "qgsrasterformatsaveoptionswidget.h"
2222
#include "qgsgenericprojectionselector.h"
2323

24+
#include "gdal.h"
25+
2426
#include <QFileDialog>
2527
#include <QMessageBox>
2628
#include <QSettings>

src/gui/qgsrasterpyramidsoptionswidget.cpp

+29-12
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
#include <QMenu>
2828
#include <QCheckBox>
2929

30-
3130
QgsRasterPyramidsOptionsWidget::QgsRasterPyramidsOptionsWidget( QWidget* parent, const QString& provider )
3231
: QWidget( parent )
3332
, mProvider( provider )
@@ -52,14 +51,14 @@ void QgsRasterPyramidsOptionsWidget::updateUi()
5251
QString prefix = mProvider + "/driverOptions/_pyramids/";
5352
QString tmpStr;
5453

55-
// cbxPyramidsInternal->setChecked( mySettings.value( prefix + "internal", false ).toBool() );
56-
tmpStr = mySettings.value( prefix + "format", "gtiff" ).toString();
54+
// keep it in sync with qgsrasterlayerproperties.cpp
55+
tmpStr = mySettings.value( prefix + "format", "external" ).toString();
5756
if ( tmpStr == "internal" )
58-
cbxPyramidsFormat->setCurrentIndex( 1 );
57+
cbxPyramidsFormat->setCurrentIndex( Format::INTERNAL );
5958
else if ( tmpStr == "external_erdas" )
60-
cbxPyramidsFormat->setCurrentIndex( 2 );
59+
cbxPyramidsFormat->setCurrentIndex( Format::ERDAS );
6160
else
62-
cbxPyramidsFormat->setCurrentIndex( 0 );
61+
cbxPyramidsFormat->setCurrentIndex( Format::GTIFF );
6362

6463
// initialize resampling methods
6564
cboResamplingMethod->clear();
@@ -68,8 +67,9 @@ void QgsRasterPyramidsOptionsWidget::updateUi()
6867
{
6968
cboResamplingMethod->addItem( method.second, method.first );
7069
}
71-
cboResamplingMethod->setCurrentIndex( cboResamplingMethod->findData(
72-
mySettings.value( prefix + "resampling", "AVERAGE" ).toString() ) );
70+
QString defaultMethod = mySettings.value( prefix + "resampling", "AVERAGE" ).toString();
71+
int idx = cboResamplingMethod->findData( defaultMethod );
72+
cboResamplingMethod->setCurrentIndex( idx );
7373

7474
// validate string, only space-separated positive integers are allowed
7575
lePyramidsLevels->setEnabled( cbxPyramidsLevelsCustom->isChecked() );
@@ -126,9 +126,9 @@ void QgsRasterPyramidsOptionsWidget::apply()
126126
QString tmpStr;
127127

128128
// mySettings.setValue( prefix + "internal", cbxPyramidsInternal->isChecked() );
129-
if ( cbxPyramidsFormat->currentIndex() == 1 )
129+
if ( cbxPyramidsFormat->currentIndex() == Format::INTERNAL )
130130
tmpStr = "internal";
131-
else if ( cbxPyramidsFormat->currentIndex() == 2 )
131+
else if ( cbxPyramidsFormat->currentIndex() == Format::ERDAS )
132132
tmpStr = "external_erdas";
133133
else
134134
tmpStr = "external";
@@ -165,8 +165,25 @@ void QgsRasterPyramidsOptionsWidget::on_cbxPyramidsLevelsCustom_toggled( bool to
165165

166166
void QgsRasterPyramidsOptionsWidget::on_cbxPyramidsFormat_currentIndexChanged( int index )
167167
{
168-
mSaveOptionsWidget->setEnabled( index != 2 );
169-
mSaveOptionsWidget->setPyramidsFormat(( QgsRaster::RasterPyramidsFormat ) index );
168+
mSaveOptionsWidget->setEnabled( index != Format::ERDAS );
169+
QgsRaster::RasterPyramidsFormat format;
170+
switch ( index )
171+
{
172+
case Format::GTIFF:
173+
format = QgsRaster::PyramidsGTiff;
174+
break;
175+
case Format::INTERNAL:
176+
format = QgsRaster::PyramidsInternal;
177+
break;
178+
case Format::ERDAS:
179+
format = QgsRaster::PyramidsErdas;
180+
break;
181+
default:
182+
QgsDebugMsg( "Should not happen !" );
183+
format = QgsRaster::PyramidsGTiff;
184+
break;
185+
}
186+
mSaveOptionsWidget->setPyramidsFormat( format );
170187
}
171188

172189
void QgsRasterPyramidsOptionsWidget::setOverviewList()

src/gui/qgsrasterpyramidsoptionswidget.h

+9
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,15 @@ class GUI_EXPORT QgsRasterPyramidsOptionsWidget: public QWidget,
6363

6464
private:
6565

66+
// Must be in the same order as in the .ui file
67+
typedef enum
68+
{
69+
GTIFF = 0,
70+
INTERNAL = 1,
71+
ERDAS = 2
72+
} Format;
73+
74+
6675
QString mProvider;
6776
QList< int > mOverviewList;
6877
QMap< int, QCheckBox* > mOverviewCheckBoxes;

src/providers/gdal/qgsgdalprovider.cpp

+31-21
Original file line numberDiff line numberDiff line change
@@ -1598,13 +1598,20 @@ QString QgsGdalProvider::buildPyramids( const QList<QgsRasterPyramid> & theRaste
15981598
Q_FOREACH ( const QString& option, theConfigOptions )
15991599
{
16001600
QStringList opt = option.split( '=' );
1601-
QByteArray key = opt[0].toLocal8Bit();
1602-
QByteArray value = opt[1].toLocal8Bit();
1603-
// save previous value
1604-
myConfigOptionsOld[ opt[0] ] = QString( CPLGetConfigOption( key.data(), nullptr ) );
1605-
// set temp. value
1606-
CPLSetConfigOption( key.data(), value.data() );
1607-
QgsDebugMsg( QString( "set option %1=%2" ).arg( key.data(), value.data() ) );
1601+
if ( opt.size() == 2 )
1602+
{
1603+
QByteArray key = opt[0].toLocal8Bit();
1604+
QByteArray value = opt[1].toLocal8Bit();
1605+
// save previous value
1606+
myConfigOptionsOld[ opt[0] ] = QString( CPLGetConfigOption( key.data(), nullptr ) );
1607+
// set temp. value
1608+
CPLSetConfigOption( key.data(), value.data() );
1609+
QgsDebugMsg( QString( "set option %1=%2" ).arg( key.data(), value.data() ) );
1610+
}
1611+
else
1612+
{
1613+
QgsDebugMsg( QString( "invalid pyramid option: %1" ).arg( option ) );
1614+
}
16081615
}
16091616
}
16101617

@@ -1634,7 +1641,8 @@ QString QgsGdalProvider::buildPyramids( const QList<QgsRasterPyramid> & theRaste
16341641
}
16351642
}
16361643
/* From : http://www.gdal.org/classGDALDataset.html#a2aa6f88b3bbc840a5696236af11dde15
1637-
* pszResampling : one of "NEAREST", "GAUSS", "CUBIC", "AVERAGE", "MODE", "AVERAGE_MAGPHASE" or "NONE" controlling the downsampling method applied.
1644+
* pszResampling : one of "NEAREST", "GAUSS", "CUBIC", "CUBICSPLINE" (GDAL >= 2.0),
1645+
* "LANCZOS" ( GDAL >= 2.0), "AVERAGE", "MODE" or "NONE" controlling the downsampling method applied.
16381646
* nOverviews : number of overviews to build.
16391647
* panOverviewList : the list of overview decimation factors to build.
16401648
* nListBands : number of bands to build overviews for in panBandList. Build for all bands if this is 0.
@@ -2962,7 +2970,7 @@ QString QgsGdalProvider::validateCreationOptions( const QStringList& createOptio
29622970
return message;
29632971
}
29642972

2965-
QString QgsGdalProvider::validatePyramidsCreationOptions( QgsRaster::RasterPyramidsFormat pyramidsFormat,
2973+
QString QgsGdalProvider::validatePyramidsConfigOptions( QgsRaster::RasterPyramidsFormat pyramidsFormat,
29662974
const QStringList & theConfigOptions, const QString & fileFormat )
29672975
{
29682976
// Erdas Imagine format does not support config options
@@ -2977,21 +2985,19 @@ QString QgsGdalProvider::validatePyramidsCreationOptions( QgsRaster::RasterPyram
29772985
else if ( pyramidsFormat == QgsRaster::PyramidsInternal )
29782986
{
29792987
QStringList supportedFormats;
2980-
supportedFormats << "gtiff" << "georaster" << "hfa" << "jp2kak" << "mrsid" << "nitf";
2988+
supportedFormats << "gtiff" << "georaster" << "hfa" << "gpkg" << "rasterlite" << "nitf";
29812989
if ( ! supportedFormats.contains( fileFormat.toLower() ) )
2982-
return QString( "Internal pyramids format only supported for gtiff/georaster/hfa/jp2kak/mrsid/nitf files (using %1)" ).arg( fileFormat );
2983-
// TODO - check arguments for georaster hfa jp2kak mrsid nitf
2984-
// for now, only test gtiff
2985-
else if ( fileFormat.toLower() != "gtiff" )
2986-
return QString();
2990+
return QString( "Internal pyramids format only supported for gtiff/georaster/gpkg/rasterlite/nitf files (using %1)" ).arg( fileFormat );
29872991
}
2988-
2989-
// for gtiff external or internal pyramids, validate gtiff-specific values
2990-
// PHOTOMETRIC_OVERVIEW=YCBCR requires a source raster with only 3 bands (RGB)
2991-
if ( theConfigOptions.contains( "PHOTOMETRIC_OVERVIEW=YCBCR" ) )
2992+
else
29922993
{
2993-
if ( GDALGetRasterCount( mGdalDataset ) != 3 )
2994-
return "PHOTOMETRIC_OVERVIEW=YCBCR requires a source raster with only 3 bands (RGB)";
2994+
// for gtiff external pyramids, validate gtiff-specific values
2995+
// PHOTOMETRIC_OVERVIEW=YCBCR requires a source raster with only 3 bands (RGB)
2996+
if ( theConfigOptions.contains( "PHOTOMETRIC_OVERVIEW=YCBCR" ) )
2997+
{
2998+
if ( GDALGetRasterCount( mGdalDataset ) != 3 )
2999+
return "PHOTOMETRIC_OVERVIEW=YCBCR requires a source raster with only 3 bands (RGB)";
3000+
}
29953001
}
29963002

29973003
return QString();
@@ -3022,6 +3028,10 @@ QGISEXTERN QList<QPair<QString, QString> > *pyramidResamplingMethods()
30223028
methods.append( QPair<QString, QString>( "AVERAGE", QObject::tr( "Average" ) ) );
30233029
methods.append( QPair<QString, QString>( "GAUSS", QObject::tr( "Gauss" ) ) );
30243030
methods.append( QPair<QString, QString>( "CUBIC", QObject::tr( "Cubic" ) ) );
3031+
#if GDAL_VERSION_MAJOR >= 2
3032+
methods.append( QPair<QString, QString>( "CUBICSPLINE", QObject::tr( "Cubic Spline" ) ) );
3033+
methods.append( QPair<QString, QString>( "LANCZOS", QObject::tr( "Lanczos" ) ) );
3034+
#endif
30253035
methods.append( QPair<QString, QString>( "MODE", QObject::tr( "Mode" ) ) );
30263036
methods.append( QPair<QString, QString>( "NONE", QObject::tr( "None" ) ) );
30273037
}

src/providers/gdal/qgsgdalprovider.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -244,8 +244,8 @@ class QgsGdalProvider : public QgsRasterDataProvider, QgsGdalProviderBase
244244
bool remove() override;
245245

246246
QString validateCreationOptions( const QStringList& createOptions, const QString& format ) override;
247-
QString validatePyramidsCreationOptions( QgsRaster::RasterPyramidsFormat pyramidsFormat,
248-
const QStringList & theConfigOptions, const QString & fileFormat );
247+
QString validatePyramidsConfigOptions( QgsRaster::RasterPyramidsFormat pyramidsFormat,
248+
const QStringList & theConfigOptions, const QString & fileFormat ) override;
249249

250250
private:
251251
// update mode

tests/src/core/testqgsrasterlayer.cpp

+36
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include <QDesktopServices>
2525

2626
#include "cpl_conv.h"
27+
#include "gdal.h"
2728

2829
//qgis includes...
2930
#include <qgsrasterlayer.h>
@@ -512,6 +513,7 @@ void TestQgsRasterLayer::buildExternalOverviews()
512513
QString myResult =
513514
mypLayer->dataProvider()->buildPyramids( myPyramidList, "NEAREST", myFormatFlag );
514515
qDebug( "%s", myResult.toLocal8Bit().constData() );
516+
QVERIFY( myResult.isEmpty() );
515517
//
516518
// Lets verify we have pyramids now...
517519
//
@@ -526,8 +528,42 @@ void TestQgsRasterLayer::buildExternalOverviews()
526528
// And that they were indeed in an external file...
527529
//
528530
QVERIFY( QFile::exists( myTempPath + "landsat.tif.ovr" ) );
531+
532+
//cleanup
533+
delete mypLayer;
534+
535+
QFile::remove( myTempPath + "landsat.tif.ovr" );
536+
mypLayer = new QgsRasterLayer( myRasterFileInfo.filePath(),
537+
myRasterFileInfo.completeBaseName() );
538+
myPyramidList = mypLayer->dataProvider()->buildPyramidList();
539+
for ( int myCounterInt = 0; myCounterInt < myPyramidList.count(); myCounterInt++ )
540+
{
541+
//mark to be pyramided
542+
myPyramidList[myCounterInt].build = true;
543+
}
544+
545+
// Test with options
546+
QStringList optionList;
547+
optionList << "COMPRESS_OVERVIEW=DEFLATE";
548+
optionList << "invalid";
549+
550+
myResult =
551+
mypLayer->dataProvider()->buildPyramids( myPyramidList, "NEAREST", myFormatFlag, optionList );
552+
qDebug( "%s", myResult.toLocal8Bit().constData() );
553+
QVERIFY( myResult.isEmpty() );
554+
QVERIFY( QFile::exists( myTempPath + "landsat.tif.ovr" ) );
555+
529556
//cleanup
530557
delete mypLayer;
558+
559+
// Check that the overview is Deflate compressed
560+
QString ovrFilename( myTempPath + "landsat.tif.ovr" );
561+
GDALDatasetH hDS = GDALOpen( ovrFilename.toLocal8Bit().constData(), GA_ReadOnly );
562+
QVERIFY( hDS );
563+
const char* pszCompression = GDALGetMetadataItem( hDS, "COMPRESSION", "IMAGE_STRUCTURE" );
564+
QVERIFY( pszCompression && EQUAL( pszCompression, "DEFLATE" ) );
565+
GDALClose( hDS );
566+
531567
mReport += "<h2>Check Overviews</h2>\n";
532568
mReport += "<p>Passed</p>";
533569
}

0 commit comments

Comments
 (0)