Skip to content

Commit e299a49

Browse files
authoredFeb 2, 2021
Merge pull request #41277 from nirvn/gdal_openoptions
[gdal] Add open options support to gain dataset flexibility / fixes
2 parents 5475a05 + 7961c92 commit e299a49

File tree

8 files changed

+369
-63
lines changed

8 files changed

+369
-63
lines changed
 

‎src/core/providers/gdal/qgsgdalprovider.cpp

Lines changed: 9 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1991,12 +1991,12 @@ QString QgsGdalProvider::buildPyramids( const QList<QgsRasterPyramid> &rasterPyr
19911991
GDALClose( mGdalDataset );
19921992
//mGdalBaseDataset = GDALOpen( QFile::encodeName( dataSourceUri() ).constData(), GA_Update );
19931993

1994-
mGdalBaseDataset = gdalOpen( dataSourceUri( true ).toUtf8().constData(), GA_Update );
1994+
mGdalBaseDataset = gdalOpen( dataSourceUri( true ), GDAL_OF_UPDATE );
19951995

19961996
// if the dataset couldn't be opened in read / write mode, tell the user
19971997
if ( !mGdalBaseDataset )
19981998
{
1999-
mGdalBaseDataset = gdalOpen( dataSourceUri( true ).toUtf8().constData(), GA_ReadOnly );
1999+
mGdalBaseDataset = gdalOpen( dataSourceUri( true ), GDAL_OF_READONLY );
20002000
//Since we are not a virtual warped dataset, mGdalDataSet and mGdalBaseDataset are supposed to be the same
20012001
mGdalDataset = mGdalBaseDataset;
20022002
return QStringLiteral( "ERROR_WRITE_FORMAT" );
@@ -2105,7 +2105,7 @@ QString QgsGdalProvider::buildPyramids( const QList<QgsRasterPyramid> &rasterPyr
21052105
//something bad happenend
21062106
//QString myString = QString (CPLGetLastError());
21072107
GDALClose( mGdalBaseDataset );
2108-
mGdalBaseDataset = gdalOpen( dataSourceUri( true ).toUtf8().constData(), mUpdate ? GA_Update : GA_ReadOnly );
2108+
mGdalBaseDataset = gdalOpen( dataSourceUri( true ), mUpdate ? GDAL_OF_UPDATE : GDAL_OF_READONLY );
21092109
//Since we are not a virtual warped dataset, mGdalDataSet and mGdalBaseDataset are supposed to be the same
21102110
mGdalDataset = mGdalBaseDataset;
21112111

@@ -2157,7 +2157,7 @@ QString QgsGdalProvider::buildPyramids( const QList<QgsRasterPyramid> &rasterPyr
21572157
QgsDebugMsgLevel( QStringLiteral( "Reopening dataset ..." ), 2 );
21582158
//close the gdal dataset and reopen it in read only mode
21592159
GDALClose( mGdalBaseDataset );
2160-
mGdalBaseDataset = gdalOpen( dataSourceUri( true ).toUtf8().constData(), mUpdate ? GA_Update : GA_ReadOnly );
2160+
mGdalBaseDataset = gdalOpen( dataSourceUri( true ), mUpdate ? GDAL_OF_UPDATE : GDAL_OF_READONLY );
21612161
//Since we are not a virtual warped dataset, mGdalDataSet and mGdalBaseDataset are supposed to be the same
21622162
mGdalDataset = mGdalBaseDataset;
21632163
}
@@ -2342,40 +2342,12 @@ QStringList QgsGdalProvider::subLayers() const
23422342

23432343
QVariantMap QgsGdalProviderMetadata::decodeUri( const QString &uri ) const
23442344
{
2345-
QString path = uri;
2346-
QString layerName;
2347-
2348-
QString vsiPrefix = qgsVsiPrefix( path );
2349-
if ( !path.isEmpty() )
2350-
path = path.mid( vsiPrefix.count() );
2351-
2352-
if ( path.indexOf( ':' ) != -1 )
2353-
{
2354-
QStringList parts = path.split( ':' );
2355-
if ( parts[0].toLower() == QLatin1String( "gpkg" ) )
2356-
{
2357-
parts.removeFirst();
2358-
// Handle windows paths - which has an extra colon - and unix paths
2359-
if ( ( parts[0].length() > 1 && parts.count() > 1 ) || parts.count() > 2 )
2360-
{
2361-
layerName = parts[parts.length() - 1];
2362-
parts.removeLast();
2363-
}
2364-
path = parts.join( ':' );
2365-
}
2366-
}
2367-
2368-
QVariantMap uriComponents;
2369-
uriComponents.insert( QStringLiteral( "path" ), path );
2370-
uriComponents.insert( QStringLiteral( "layerName" ), layerName );
2371-
return uriComponents;
2345+
return QgsGdalProviderBase::decodeGdalUri( uri );
23722346
}
23732347

23742348
QString QgsGdalProviderMetadata::encodeUri( const QVariantMap &parts ) const
23752349
{
2376-
QString path = parts.value( QStringLiteral( "path" ) ).toString();
2377-
QString layerName = parts.value( QStringLiteral( "layerName" ) ).toString();
2378-
return path + ( !layerName.isEmpty() ? QStringLiteral( "|%1" ).arg( layerName ) : QString() );
2350+
return QgsGdalProviderBase::encodeGdalUri( parts );
23792351
}
23802352

23812353
bool QgsGdalProviderMetadata::uriIsBlocklisted( const QString &uri ) const
@@ -2629,7 +2601,7 @@ bool QgsGdalProvider::isValidRasterFileName( QString const &fileNameQString, QSt
26292601

26302602
//open the file using gdal making sure we have handled locale properly
26312603
//myDataset = GDALOpen( QFile::encodeName( fileNameQString ).constData(), GA_ReadOnly );
2632-
myDataset.reset( QgsGdalProviderBase::gdalOpen( fileName.toUtf8().constData(), GA_ReadOnly ) );
2604+
myDataset.reset( QgsGdalProviderBase::gdalOpen( fileName, GDAL_OF_READONLY ) );
26332605
if ( !myDataset )
26342606
{
26352607
if ( CPLGetLastErrorNo() != CPLE_OpenFailed )
@@ -2929,7 +2901,7 @@ bool QgsGdalProvider::initIfNeeded()
29292901
gdalUri = dataSourceUri( true );
29302902

29312903
CPLErrorReset();
2932-
mGdalBaseDataset = gdalOpen( gdalUri.toUtf8().constData(), mUpdate ? GA_Update : GA_ReadOnly );
2904+
mGdalBaseDataset = gdalOpen( gdalUri, mUpdate ? GDAL_OF_UPDATE : GDAL_OF_READONLY );
29332905

29342906
if ( !mGdalBaseDataset )
29352907
{
@@ -3524,7 +3496,7 @@ bool QgsGdalProvider::setEditable( bool enabled )
35243496
mUpdate = enabled;
35253497

35263498
// reopen the dataset
3527-
mGdalBaseDataset = gdalOpen( dataSourceUri( true ).toUtf8().constData(), mUpdate ? GA_Update : GA_ReadOnly );
3499+
mGdalBaseDataset = gdalOpen( dataSourceUri( true ), mUpdate ? GDAL_OF_UPDATE : GDAL_OF_READONLY );
35283500
if ( !mGdalBaseDataset )
35293501
{
35303502
QString msg = QStringLiteral( "Cannot reopen GDAL dataset %1:\n%2" ).arg( dataSourceUri(), QString::fromUtf8( CPLGetLastErrorMsg() ) );

‎src/core/providers/gdal/qgsgdalproviderbase.cpp

Lines changed: 93 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
#define CPL_SUPRESS_CPLUSPLUS //#spellok
2222
#include <cpl_conv.h>
23+
#include <cpl_string.h>
2324

2425
#include "qgsapplication.h"
2526
#include "qgslogger.h"
@@ -254,14 +255,29 @@ QgsRectangle QgsGdalProviderBase::extent( GDALDatasetH gdalDataset )const
254255
return extent;
255256
}
256257

257-
GDALDatasetH QgsGdalProviderBase::gdalOpen( const char *pszFilename, GDALAccess eAccess )
258+
GDALDatasetH QgsGdalProviderBase::gdalOpen( const QString &uri, unsigned int nOpenFlags )
258259
{
260+
QVariantMap parts = decodeGdalUri( uri );
261+
QString filePath = parts.value( QStringLiteral( "path" ) ).toString();
262+
const QStringList openOptions = parts.value( QStringLiteral( "openOptions" ) ).toStringList();
263+
parts.remove( QStringLiteral( "openOptions" ) );
264+
265+
char **papszOpenOptions = nullptr;
266+
for ( const QString &option : openOptions )
267+
{
268+
papszOpenOptions = CSLAddString( papszOpenOptions,
269+
option.toUtf8().constData() );
270+
}
271+
259272
bool modify_OGR_GPKG_FOREIGN_KEY_CHECK = !CPLGetConfigOption( "OGR_GPKG_FOREIGN_KEY_CHECK", nullptr );
260273
if ( modify_OGR_GPKG_FOREIGN_KEY_CHECK )
261274
{
262275
CPLSetThreadLocalConfigOption( "OGR_GPKG_FOREIGN_KEY_CHECK", "NO" );
263276
}
264-
GDALDatasetH hDS = GDALOpen( pszFilename, eAccess );
277+
278+
GDALDatasetH hDS = GDALOpenEx( encodeGdalUri( parts ).toUtf8().constData(), nOpenFlags, nullptr, papszOpenOptions, nullptr );
279+
CSLDestroy( papszOpenOptions );
280+
265281
if ( modify_OGR_GPKG_FOREIGN_KEY_CHECK )
266282
{
267283
CPLSetThreadLocalConfigOption( "OGR_GPKG_FOREIGN_KEY_CHECK", nullptr );
@@ -304,4 +320,79 @@ int QgsGdalProviderBase::gdalGetOverviewCount( GDALRasterBandH hBand )
304320
return count;
305321
}
306322

323+
QVariantMap QgsGdalProviderBase::decodeGdalUri( const QString &uri )
324+
{
325+
QString path = uri;
326+
QString layerName;
327+
QStringList openOptions;
328+
329+
QString vsiPrefix = qgsVsiPrefix( path );
330+
if ( !path.isEmpty() )
331+
path = path.mid( vsiPrefix.count() );
332+
333+
if ( path.indexOf( ':' ) != -1 )
334+
{
335+
QStringList parts = path.split( ':' );
336+
if ( parts[0].toLower() == QLatin1String( "gpkg" ) )
337+
{
338+
parts.removeFirst();
339+
// Handle windows paths - which has an extra colon - and unix paths
340+
if ( ( parts[0].length() > 1 && parts.count() > 1 ) || parts.count() > 2 )
341+
{
342+
layerName = parts[parts.length() - 1];
343+
parts.removeLast();
344+
}
345+
path = parts.join( ':' );
346+
}
347+
}
348+
349+
if ( path.contains( '|' ) )
350+
{
351+
const QRegularExpression openOptionRegex( QStringLiteral( "\\|option:([^|]*)" ) );
352+
353+
while ( true )
354+
{
355+
QRegularExpressionMatch match = openOptionRegex.match( path );
356+
if ( match.hasMatch() )
357+
{
358+
openOptions << match.captured( 1 );
359+
path = path.remove( match.capturedStart( 0 ), match.capturedLength( 0 ) );
360+
}
361+
else
362+
{
363+
break;
364+
}
365+
}
366+
}
367+
368+
QVariantMap uriComponents;
369+
uriComponents.insert( QStringLiteral( "path" ), path );
370+
uriComponents.insert( QStringLiteral( "layerName" ), layerName );
371+
if ( !openOptions.isEmpty() )
372+
uriComponents.insert( QStringLiteral( "openOptions" ), openOptions );
373+
return uriComponents;
374+
}
375+
376+
QString QgsGdalProviderBase::encodeGdalUri( const QVariantMap &parts )
377+
{
378+
QString path = parts.value( QStringLiteral( "path" ) ).toString();
379+
QString layerName = parts.value( QStringLiteral( "layerName" ) ).toString();
380+
QString uri;
381+
382+
if ( !layerName.isEmpty() && path.endsWith( QStringLiteral( "gpkg" ) ) )
383+
uri = QStringLiteral( "GPKG:%1:%2" ).arg( path, layerName );
384+
else
385+
uri = path + ( !layerName.isEmpty() ? QStringLiteral( "|%1" ).arg( layerName ) : QString() );
386+
387+
const QStringList openOptions = parts.value( QStringLiteral( "openOptions" ) ).toStringList();
388+
389+
for ( const QString &openOption : openOptions )
390+
{
391+
uri += QStringLiteral( "|option:" );
392+
uri += openOption;
393+
}
394+
395+
return uri;
396+
}
397+
307398
///@endcond

‎src/core/providers/gdal/qgsgdalproviderbase.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,18 @@ class QgsGdalProviderBase
4242
static void registerGdalDrivers();
4343

4444
//! Wrapper function for GDALOpen to get around possible bugs in GDAL
45-
static GDALDatasetH gdalOpen( const char *pszFilename, GDALAccess eAccess );
45+
static GDALDatasetH gdalOpen( const QString &uri, unsigned int nOpenFlags );
4646

4747
//! Wrapper function for GDALRasterIO to get around possible bugs in GDAL
4848
static CPLErr gdalRasterIO( GDALRasterBandH hBand, GDALRWFlag eRWFlag, int nXOff, int nYOff, int nXSize, int nYSize, void *pData, int nBufXSize, int nBufYSize, GDALDataType eBufType, int nPixelSpace, int nLineSpace, QgsRasterBlockFeedback *feedback = nullptr );
4949

5050
//! Wrapper function for GDALRasterIO to get around possible bugs in GDAL
5151
static int gdalGetOverviewCount( GDALRasterBandH hBand );
52+
53+
static QVariantMap decodeGdalUri( const QString &uri );
54+
55+
static QString encodeGdalUri( const QVariantMap &parts );
56+
5257
protected:
5358

5459
Qgis::DataType dataTypeFromGdal( GDALDataType gdalDataType ) const;

‎src/gui/providers/gdal/qgsgdalsourceselect.cpp

Lines changed: 200 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include "ogr/qgsogrhelperfunctions.h"
2525

2626
#include <gdal.h>
27+
#include <cpl_minixml.h>
2728

2829
QgsGdalSourceSelect::QgsGdalSourceSelect( QWidget *parent, Qt::WindowFlags fl, QgsProviderRegistry::WidgetMode widgetMode ):
2930
QgsAbstractDataSourceWidget( parent, fl, widgetMode )
@@ -54,17 +55,26 @@ QgsGdalSourceSelect::QgsGdalSourceSelect( QWidget *parent, Qt::WindowFlags fl, Q
5455
connect( protocolURI, &QLineEdit::textChanged, this, [ = ]( const QString & text )
5556
{
5657
if ( radioSrcProtocol->isChecked() )
58+
{
5759
emit enableButtons( !text.isEmpty() );
60+
fillOpenOptions();
61+
}
5862
} );
5963
connect( mBucket, &QLineEdit::textChanged, this, [ = ]( const QString & text )
6064
{
6165
if ( radioSrcProtocol->isChecked() )
66+
{
6267
emit enableButtons( !text.isEmpty() && !mKey->text().isEmpty() );
68+
fillOpenOptions();
69+
}
6370
} );
6471
connect( mKey, &QLineEdit::textChanged, this, [ = ]( const QString & text )
6572
{
6673
if ( radioSrcProtocol->isChecked() )
74+
{
6775
emit enableButtons( !text.isEmpty() && !mBucket->text().isEmpty() );
76+
fillOpenOptions();
77+
}
6878
} );
6979

7080
mFileWidget->setDialogTitle( tr( "Open GDAL Supported Raster Dataset(s)" ) );
@@ -75,7 +85,9 @@ QgsGdalSourceSelect::QgsGdalSourceSelect( QWidget *parent, Qt::WindowFlags fl, Q
7585
{
7686
mRasterPath = path;
7787
emit enableButtons( ! mRasterPath.isEmpty() );
88+
fillOpenOptions();
7889
} );
90+
mOpenOptionsGroupBox->setVisible( false );
7991
}
8092

8193
bool QgsGdalSourceSelect::isProtocolCloudType()
@@ -119,8 +131,10 @@ void QgsGdalSourceSelect::radioSrcFile_toggled( bool checked )
119131
{
120132
fileGroupBox->show();
121133
protocolGroupBox->hide();
134+
clearOpenOptions();
122135

123136
emit enableButtons( !mFileWidget->filePath().isEmpty() );
137+
124138
}
125139
}
126140

@@ -130,8 +144,8 @@ void QgsGdalSourceSelect::radioSrcProtocol_toggled( bool checked )
130144
{
131145
fileGroupBox->hide();
132146
protocolGroupBox->show();
133-
134147
setProtocolWidgetsVisibility();
148+
clearOpenOptions();
135149

136150
emit enableButtons( !protocolURI->text().isEmpty() );
137151
}
@@ -141,41 +155,72 @@ void QgsGdalSourceSelect::cmbProtocolTypes_currentIndexChanged( const QString &t
141155
{
142156
Q_UNUSED( text )
143157
setProtocolWidgetsVisibility();
158+
clearOpenOptions();
144159
}
145160

146161
void QgsGdalSourceSelect::addButtonClicked()
147162
{
148-
if ( radioSrcFile->isChecked() )
163+
computeDataSources();
164+
165+
if ( mDataSources.isEmpty() )
166+
{
167+
QMessageBox::information( this,
168+
tr( "Add raster layer" ),
169+
tr( "No layers selected." ) );
170+
return;
171+
}
172+
173+
for ( const QString &dataSource : mDataSources )
174+
{
175+
if ( QFile::exists( dataSource ) )
176+
emit addRasterLayer( dataSource, QFileInfo( dataSource ).completeBaseName(), QStringLiteral( "gdal" ) );
177+
else
178+
emit addRasterLayer( dataSource, dataSource, QStringLiteral( "gdal" ) );
179+
}
180+
}
181+
182+
void QgsGdalSourceSelect::computeDataSources()
183+
{
184+
mDataSources.clear();
185+
186+
QStringList openOptions;
187+
for ( QWidget *control : mOpenOptionsWidgets )
149188
{
150-
if ( mRasterPath.isEmpty() )
189+
QString value;
190+
if ( QComboBox *cb = qobject_cast<QComboBox *>( control ) )
151191
{
152-
QMessageBox::information( this,
153-
tr( "Add raster layer" ),
154-
tr( "No layers selected." ) );
155-
return;
192+
value = cb->itemData( cb->currentIndex() ).toString();
156193
}
194+
else if ( QLineEdit *le = qobject_cast<QLineEdit *>( control ) )
195+
{
196+
value = le->text();
197+
}
198+
if ( !value.isEmpty() )
199+
{
200+
openOptions << QStringLiteral( "%1=%2" ).arg( control->objectName() ).arg( value );
201+
}
202+
}
157203

158-
const QStringList paths = QgsFileWidget::splitFilePaths( mRasterPath );
159-
for ( const QString &path : paths )
204+
if ( radioSrcFile->isChecked() )
205+
{
206+
for ( const auto &filePath : QgsFileWidget::splitFilePaths( mRasterPath ) )
160207
{
161-
emit addRasterLayer( path, QFileInfo( path ).completeBaseName(), QStringLiteral( "gdal" ) );
208+
QVariantMap parts;
209+
if ( !openOptions.isEmpty() )
210+
parts.insert( QStringLiteral( "openOptions" ), openOptions );
211+
parts.insert( QStringLiteral( "path" ), filePath );
212+
mDataSources << QgsProviderRegistry::instance()->encodeUri( QStringLiteral( "gdal" ), parts );
162213
}
163214
}
164215
else if ( radioSrcProtocol->isChecked() )
165216
{
166217
bool cloudType = isProtocolCloudType();
167218
if ( !cloudType && protocolURI->text().isEmpty() )
168219
{
169-
QMessageBox::information( this,
170-
tr( "Add raster layer" ),
171-
tr( "No protocol URI entered." ) );
172220
return;
173221
}
174222
else if ( cloudType && ( mBucket->text().isEmpty() || mKey->text().isEmpty() ) )
175223
{
176-
QMessageBox::information( this,
177-
tr( "Add raster layer" ),
178-
tr( "No protocol bucket and/or key entered." ) );
179224
return;
180225
}
181226

@@ -189,13 +234,146 @@ void QgsGdalSourceSelect::addButtonClicked()
189234
uri = protocolURI->text();
190235
}
191236

192-
QString dataSource = createProtocolURI( cmbProtocolTypes->currentText(),
193-
uri,
194-
mAuthSettingsProtocol->configId(),
195-
mAuthSettingsProtocol->username(),
196-
mAuthSettingsProtocol->password() );
197-
emit addRasterLayer( dataSource, dataSource, QStringLiteral( "gdal" ) );
237+
QVariantMap parts;
238+
if ( !openOptions.isEmpty() )
239+
parts.insert( QStringLiteral( "openOptions" ), openOptions );
240+
parts.insert( QStringLiteral( "path" ),
241+
createProtocolURI( cmbProtocolTypes->currentText(),
242+
uri,
243+
mAuthSettingsProtocol->configId(),
244+
mAuthSettingsProtocol->username(),
245+
mAuthSettingsProtocol->password() ) );
246+
mDataSources << QgsProviderRegistry::instance()->encodeUri( QStringLiteral( "gdal" ), parts );
247+
}
248+
}
249+
250+
void QgsGdalSourceSelect::clearOpenOptions()
251+
{
252+
mOpenOptionsWidgets.clear();
253+
mOpenOptionsGroupBox->setVisible( false );
254+
mOpenOptionsLabel->clear();
255+
while ( mOpenOptionsLayout->count() )
256+
{
257+
QLayoutItem *item = mOpenOptionsLayout->takeAt( 0 );
258+
delete item->widget();
259+
delete item;
260+
}
261+
}
262+
263+
void QgsGdalSourceSelect::fillOpenOptions()
264+
{
265+
clearOpenOptions();
266+
computeDataSources();
267+
if ( mDataSources.isEmpty() )
268+
return;
269+
270+
GDALDriverH hDriver;
271+
hDriver = GDALIdentifyDriver( mDataSources[0].toUtf8().toStdString().c_str(), nullptr );
272+
if ( hDriver == nullptr )
273+
return;
274+
275+
const char *pszOpenOptionList = GDALGetMetadataItem( hDriver, GDAL_DMD_OPENOPTIONLIST, nullptr );
276+
if ( pszOpenOptionList == nullptr )
277+
return;
278+
279+
CPLXMLNode *psDoc = CPLParseXMLString( pszOpenOptionList );
280+
if ( psDoc == nullptr )
281+
return;
282+
CPLXMLNode *psOpenOptionList = CPLGetXMLNode( psDoc, "=OpenOptionList" );
283+
if ( psOpenOptionList == nullptr )
284+
{
285+
CPLDestroyXMLNode( psDoc );
286+
return;
287+
}
288+
289+
for ( auto psItem = psOpenOptionList->psChild; psItem != nullptr; psItem = psItem->psNext )
290+
{
291+
if ( psItem->eType != CXT_Element || !EQUAL( psItem->pszValue, "Option" ) )
292+
continue;
293+
294+
const char *pszOptionName = CPLGetXMLValue( psItem, "name", nullptr );
295+
if ( pszOptionName == nullptr )
296+
continue;
297+
298+
// Exclude options that are not of raster scope
299+
const char *pszScope = CPLGetXMLValue( psItem, "scope", nullptr );
300+
if ( pszScope != nullptr && strstr( pszScope, "raster" ) == nullptr )
301+
continue;
302+
303+
const char *pszType = CPLGetXMLValue( psItem, "type", nullptr );
304+
QStringList options;
305+
if ( pszType && EQUAL( pszType, "string-select" ) )
306+
{
307+
for ( auto psOption = psItem->psChild; psOption != nullptr; psOption = psOption->psNext )
308+
{
309+
if ( psOption->eType != CXT_Element ||
310+
!EQUAL( psOption->pszValue, "Value" ) ||
311+
psOption->psChild == nullptr )
312+
{
313+
continue;
314+
}
315+
options << psOption->psChild->pszValue;
316+
}
317+
}
318+
319+
QLabel *label = new QLabel( pszOptionName );
320+
QWidget *control = nullptr;
321+
if ( pszType && EQUAL( pszType, "boolean" ) )
322+
{
323+
QComboBox *cb = new QComboBox();
324+
cb->addItem( tr( "Yes" ), "YES" );
325+
cb->addItem( tr( "No" ), "NO" );
326+
cb->addItem( tr( "<Default>" ), QVariant( QVariant::String ) );
327+
int idx = cb->findData( QVariant( QVariant::String ) );
328+
cb->setCurrentIndex( idx );
329+
control = cb;
330+
}
331+
else if ( !options.isEmpty() )
332+
{
333+
QComboBox *cb = new QComboBox();
334+
for ( const QString &val : qgis::as_const( options ) )
335+
{
336+
cb->addItem( val, val );
337+
}
338+
cb->addItem( tr( "<Default>" ), QVariant( QVariant::String ) );
339+
int idx = cb->findData( QVariant( QVariant::String ) );
340+
cb->setCurrentIndex( idx );
341+
control = cb;
342+
}
343+
else
344+
{
345+
QLineEdit *le = new QLineEdit( );
346+
control = le;
347+
}
348+
control->setObjectName( pszOptionName );
349+
mOpenOptionsWidgets.push_back( control );
350+
351+
const char *pszDescription = CPLGetXMLValue( psItem, "description", nullptr );
352+
if ( pszDescription )
353+
{
354+
label->setToolTip( QStringLiteral( "<p>%1</p>" ).arg( pszDescription ) );
355+
control->setToolTip( QStringLiteral( "<p>%1</p>" ).arg( pszDescription ) );
356+
}
357+
mOpenOptionsLayout->addRow( label, control );
198358
}
359+
360+
CPLDestroyXMLNode( psDoc );
361+
362+
// Set label to point to driver help page
363+
const char *pszHelpTopic = GDALGetMetadataItem( hDriver, GDAL_DMD_HELPTOPIC, nullptr );
364+
if ( pszHelpTopic )
365+
{
366+
mOpenOptionsLabel->setText( tr( "Consult <a href=\"https://gdal.org/%1\">%2 driver help page</a> for detailed explanations on options" ).arg( pszHelpTopic ).arg( GDALGetDriverShortName( hDriver ) ) );
367+
mOpenOptionsLabel->setTextInteractionFlags( Qt::TextBrowserInteraction );
368+
mOpenOptionsLabel->setOpenExternalLinks( true );
369+
mOpenOptionsLabel->setVisible( true );
370+
}
371+
else
372+
{
373+
mOpenOptionsLabel->setVisible( false );
374+
}
375+
376+
mOpenOptionsGroupBox->setVisible( !mOpenOptionsWidgets.empty() );
199377
}
200378

201379
///@endcond

‎src/gui/providers/gdal/qgsgdalsourceselect.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,13 @@ class QgsGdalSourceSelect : public QgsAbstractDataSourceWidget, private Ui::QgsG
5252

5353
private:
5454

55+
void computeDataSources();
56+
void clearOpenOptions();
57+
void fillOpenOptions();
58+
std::vector<QWidget *> mOpenOptionsWidgets;
59+
5560
QString mRasterPath;
61+
QStringList mDataSources;
5662

5763
};
5864

‎src/ui/qgsgdalsourceselectbase.ui

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,29 @@
207207
</layout>
208208
</widget>
209209
</item>
210+
<item>
211+
<widget class="QgsCollapsibleGroupBox" name="mOpenOptionsGroupBox">
212+
<property name="title">
213+
<string>Options</string>
214+
</property>
215+
<layout class="QVBoxLayout" name="openOptionsVBoxLayout">
216+
<item>
217+
<widget class="QLabel" name="mOpenOptionsLabel">
218+
<property name="text">
219+
<string></string>
220+
</property>
221+
</widget>
222+
</item>
223+
<item>
224+
<layout class="QFormLayout" name="mOpenOptionsLayout">
225+
<property name="fieldGrowthPolicy">
226+
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
227+
</property>
228+
</layout>
229+
</item>
230+
</layout>
231+
</widget>
232+
</item>
210233
</layout>
211234
</item>
212235
<item row="1" column="0">
@@ -246,6 +269,7 @@
246269
</customwidget>
247270
</customwidgets>
248271
<tabstops>
272+
<tabstop>mOpenOptionsGroupBox</tabstop>
249273
<tabstop>buttonBox</tabstop>
250274
</tabstops>
251275
<resources/>

‎tests/src/core/testqgsgdalprovider.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ void TestQgsGdalProvider::encodeUri()
117117
QCOMPARE( QgsProviderRegistry::instance()->encodeUri( QStringLiteral( "gdal" ), parts ), QStringLiteral( "/home/user/test.gpkg" ) );
118118

119119
parts.insert( QStringLiteral( "layerName" ), QStringLiteral( "layername" ) );
120-
QCOMPARE( QgsProviderRegistry::instance()->encodeUri( QStringLiteral( "gdal" ), parts ), QStringLiteral( "/home/user/test.gpkg|layername" ) );
120+
QCOMPARE( QgsProviderRegistry::instance()->encodeUri( QStringLiteral( "gdal" ), parts ), QStringLiteral( "GPKG:/home/user/test.gpkg:layername" ) );
121121
}
122122

123123
void TestQgsGdalProvider::scaleDataType()

‎tests/src/python/test_provider_gdal.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,36 @@ def testRasterBlock(self):
8181
block = raster_layer.dataProvider().block(1, extent, 3, 1)
8282
self.checkBlockContents(block, full_content[row * 3:row * 3 + 3])
8383

84+
def testDecodeEncodeUriGpkg(self):
85+
"""Test decodeUri/encodeUri geopackage support"""
86+
87+
uri = '/my/raster.gpkg'
88+
parts = QgsProviderRegistry.instance().decodeUri('gdal', uri)
89+
self.assertEqual(parts, {'path': '/my/raster.gpkg', 'layerName': None})
90+
encodedUri = QgsProviderRegistry.instance().encodeUri('gdal', parts)
91+
self.assertEqual(encodedUri, uri)
92+
93+
uri = 'GPKG:/my/raster.gpkg'
94+
parts = QgsProviderRegistry.instance().decodeUri('gdal', uri)
95+
self.assertEqual(parts, {'path': '/my/raster.gpkg', 'layerName': None})
96+
encodedUri = QgsProviderRegistry.instance().encodeUri('gdal', parts)
97+
self.assertEqual(encodedUri, '/my/raster.gpkg')
98+
99+
uri = 'GPKG:/my/raster.gpkg:mylayer'
100+
parts = QgsProviderRegistry.instance().decodeUri('gdal', uri)
101+
self.assertEqual(parts, {'path': '/my/raster.gpkg', 'layerName': 'mylayer'})
102+
encodedUri = QgsProviderRegistry.instance().encodeUri('gdal', parts)
103+
self.assertEqual(encodedUri, uri)
104+
105+
def testDecodeEncodeUriOptions(self):
106+
"""Test decodeUri/encodeUri options support"""
107+
108+
uri = '/my/raster.pdf|option:DPI=300|option:GIVEME=TWO'
109+
parts = QgsProviderRegistry.instance().decodeUri('gdal', uri)
110+
self.assertEqual(parts, {'path': '/my/raster.pdf', 'layerName': None, 'openOptions': ['DPI=300', 'GIVEME=TWO']})
111+
encodedUri = QgsProviderRegistry.instance().encodeUri('gdal', parts)
112+
self.assertEqual(encodedUri, uri)
113+
84114

85115
if __name__ == '__main__':
86116
unittest.main()

0 commit comments

Comments
 (0)
Please sign in to comment.