1 /***************************************************************************
2 qgsogrdataitems.cpp
3 -------------------
4 begin : 2011-04-01
5 copyright : (C) 2011 Radim Blazek
6 email : radim dot blazek at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16 #include "qgsogrdataitems.h"
17 ///@cond PRIVATE
18
19 #include "qgsogrdbconnection.h"
20
21 #include "qgslogger.h"
22 #include "qgsmessagelog.h"
23 #include "qgssettings.h"
24 #include "qgsproject.h"
25 #include "qgsvectorlayer.h"
26 #include "qgsrasterlayer.h"
27 #include "qgsgeopackagedataitems.h"
28 #include "qgsogrutils.h"
29 #include "qgsproviderregistry.h"
30 #include "qgssqliteutils.h"
31 #include "symbology/qgsstyle.h"
32
33 #include <QFileInfo>
34 #include <QTextStream>
35 #include <QAction>
36 #include <QMessageBox>
37 #include <QInputDialog>
38 #include <QFileDialog>
39 #include <QRegularExpression>
40
41 #include <ogr_srs_api.h>
42 #include <cpl_error.h>
43 #include <cpl_conv.h>
44 #include <gdal.h>
45
QgsOgrLayerItem(QgsDataItem * parent,const QString & name,const QString & path,const QString & uri,LayerType layerType,const QString & driverName,bool isSubLayer)46 QgsOgrLayerItem::QgsOgrLayerItem( QgsDataItem *parent,
47 const QString &name,
48 const QString &path,
49 const QString &uri,
50 LayerType layerType,
51 const QString &driverName,
52 bool isSubLayer )
53 : QgsLayerItem( parent, name, path, uri, layerType, QStringLiteral( "ogr" ) )
54 , mDriverName( driverName )
55 , mIsSubLayer( isSubLayer )
56 {
57 mIsSubLayer = isSubLayer;
58 mToolTip = uri;
59 const bool isIndex { QRegularExpression( R"(=idx_[^_]+_[^_]+.*$)" ).match( uri ).hasMatch() };
60 setState( ( driverName == QStringLiteral( "SQLite" ) && ! isIndex ) ? NotPopulated : Populated ); // children are accepted except for sqlite
61 }
62
63
createChildren()64 QVector<QgsDataItem *> QgsOgrLayerItem::createChildren()
65 {
66 QVector<QgsDataItem *> children;
67 // Geopackage is handled by QgsGeoPackageVectorLayerItem and QgsGeoPackageRasterLayerItem
68 // Proxy to spatialite provider data items because it implements the connections API
69 if ( mDriverName == QLatin1String( "SQLite" ) )
70 {
71 children.push_back( new QgsFieldsItem( this,
72 path() + QStringLiteral( "/columns/ " ),
73 QStringLiteral( R"(dbname="%1")" ).arg( parent()->path().replace( '"', QLatin1String( R"(\")" ) ) ),
74 QStringLiteral( "spatialite" ), QString(), name() ) );
75 }
76 return children;
77 }
78
79
layerTypeFromDb(const QString & geometryType)80 QgsLayerItem::LayerType QgsOgrLayerItem::layerTypeFromDb( const QString &geometryType )
81 {
82 if ( geometryType.contains( QStringLiteral( "Point" ), Qt::CaseInsensitive ) )
83 {
84 return QgsLayerItem::LayerType::Point;
85 }
86 else if ( geometryType.contains( QStringLiteral( "Polygon" ), Qt::CaseInsensitive ) )
87 {
88 return QgsLayerItem::LayerType::Polygon;
89 }
90 else if ( geometryType.contains( QStringLiteral( "LineString" ), Qt::CaseInsensitive ) )
91 {
92 return QgsLayerItem::LayerType::Line;
93 }
94 else if ( geometryType.contains( QStringLiteral( "Collection" ), Qt::CaseInsensitive ) )
95 {
96 return QgsLayerItem::LayerType::Vector;
97 }
98 // To be moved in a parent class that would also work for gdal and rasters
99 else if ( geometryType.contains( QStringLiteral( "Raster" ), Qt::CaseInsensitive ) )
100 {
101 return QgsLayerItem::LayerType::Raster;
102 }
103
104 // fallback - try parsing as a WKT type string
105 switch ( QgsWkbTypes::geometryType( QgsWkbTypes::parseType( geometryType ) ) )
106 {
107 case QgsWkbTypes::PointGeometry:
108 return QgsLayerItem::LayerType::Point;
109 case QgsWkbTypes::LineGeometry:
110 return QgsLayerItem::LayerType::Line;
111 case QgsWkbTypes::PolygonGeometry:
112 return QgsLayerItem::LayerType::Polygon;
113 case QgsWkbTypes::UnknownGeometry:
114 case QgsWkbTypes::NullGeometry:
115 break;
116 }
117
118 return QgsLayerItem::LayerType::TableLayer;
119 }
120
isSubLayer() const121 bool QgsOgrLayerItem::isSubLayer() const
122 {
123 return mIsSubLayer;
124 }
125
subLayers(const QString & path,const QString & driver)126 QList<QgsOgrDbLayerInfo *> QgsOgrLayerItem::subLayers( const QString &path, const QString &driver )
127 {
128
129 QList<QgsOgrDbLayerInfo *> children;
130
131 // Vector layers
132 const QgsVectorLayer::LayerOptions layerOptions { QgsProject::instance()->transformContext() };
133 QgsVectorLayer layer( path, QStringLiteral( "ogr_tmp" ), QStringLiteral( "ogr" ), layerOptions );
134 if ( layer.isValid( ) )
135 {
136 QVariantMap oriParts = QgsOgrProviderMetadata().decodeUri( path );
137
138 // Collect mixed-geom layers
139 QMultiMap<int, QStringList> subLayersMap;
140 QgsOgrProvider *ogrProvider = qobject_cast<QgsOgrProvider *>( layer.dataProvider() );
141 const QStringList subLayersList( ogrProvider->subLayersWithoutFeatureCount( ) );
142 QMap< QString, int > mapLayerNameToCount;
143 bool uniqueNames = true;
144 int prevIdx = -1;
145 for ( const QString &descriptor : subLayersList )
146 {
147 QStringList pieces = descriptor.split( QgsDataProvider::sublayerSeparator() );
148 int idx = pieces[0].toInt();
149 subLayersMap.insert( idx, pieces );
150 if ( pieces.count() >= 4 && idx != prevIdx )
151 {
152 QString layerName = pieces[1];
153 int count = ++mapLayerNameToCount[layerName];
154 if ( count > 1 || layerName.isEmpty() )
155 uniqueNames = false;
156 }
157 prevIdx = idx;
158 }
159 prevIdx = -1;
160 const auto subLayerKeys = subLayersMap.keys( );
161 for ( const int &idx : subLayerKeys )
162 {
163 if ( idx == prevIdx )
164 {
165 continue;
166 }
167 prevIdx = idx;
168 QList<QStringList> values = subLayersMap.values( idx );
169 for ( int i = 0; i < values.size(); ++i )
170 {
171 QStringList pieces = values.at( i );
172 QString layerId = pieces[0];
173 QString name = pieces[1];
174 // QString featuresCount = pieces[2]; // Not used
175 QString geometryType = pieces[3];
176 QString geometryColumn = pieces[4];
177 QgsLayerItem::LayerType layerType;
178 layerType = QgsOgrLayerItem::layerTypeFromDb( geometryType );
179 // example URI for mixed-geoms geoms: '/path/gdal_sample_v1.2_no_extensions.gpkg|layerid=7|geometrytype=Point'
180 // example URI for mixed-geoms attr table: '/path/gdal_sample_v1.2_no_extensions.gpkg|layername=MyLayer|layerid=7'
181 // example URI for single geoms: '/path/gdal_sample_v1.2_no_extensions.gpkg|layerid=6'
182 if ( layerType != QgsLayerItem::LayerType::NoType )
183 {
184 if ( geometryType.contains( QStringLiteral( "Collection" ), Qt::CaseInsensitive ) )
185 {
186 QgsDebugMsgLevel( QStringLiteral( "Layer %1 is a geometry collection: skipping %2" ).arg( name, path ), 3 );
187 }
188 else
189 {
190 QVariantMap parts( oriParts );
191 if ( uniqueNames )
192 parts.insert( QStringLiteral( "layerName" ), name );
193 else
194 parts.insert( QStringLiteral( "layerId" ), layerId );
195 if ( values.size() > 1 )
196 {
197 parts.insert( QStringLiteral( "geometryType" ), geometryType );
198 }
199 QString uri = QgsOgrProviderMetadata().encodeUri( parts );
200 QgsDebugMsgLevel( QStringLiteral( "Adding %1 Vector item %2 %3 %4" ).arg( driver, name, uri, geometryType ), 3 );
201 children.append( new QgsOgrDbLayerInfo( path, uri, name, geometryColumn, geometryType, layerType, driver ) );
202 }
203 }
204 else
205 {
206 QgsDebugMsgLevel( QStringLiteral( "Layer type is not a supported %1 Vector layer %2" ).arg( driver, path ), 3 );
207 QVariantMap parts( oriParts );
208 parts.insert( QStringLiteral( "layerId" ), layerId );
209 parts.insert( QStringLiteral( "layerName" ), name );
210 QString uri = QgsOgrProviderMetadata().encodeUri( parts );
211 children.append( new QgsOgrDbLayerInfo( path, uri, name, geometryColumn, geometryType, QgsLayerItem::LayerType::TableLayer, driver ) );
212 }
213 }
214 }
215 }
216
217 // Raster layers
218 QgsRasterLayer::LayerOptions options;
219 options.loadDefaultStyle = false;
220 QgsRasterLayer rlayer( path, QStringLiteral( "gdal_tmp" ), QStringLiteral( "gdal" ), options );
221 if ( !rlayer.dataProvider()->subLayers( ).empty() )
222 {
223 const QStringList layers( rlayer.dataProvider()->subLayers( ) );
224 for ( const QString &uri : layers )
225 {
226 // Split on ':' since this is what comes out from the provider
227 QStringList pieces = uri.split( ':' );
228 QString name = pieces.value( pieces.length() - 1 );
229 QgsDebugMsgLevel( QStringLiteral( "Adding GeoPackage Raster item %1 %2" ).arg( name, uri ), 3 );
230 children.append( new QgsOgrDbLayerInfo( path, uri, name, QString(), QStringLiteral( "Raster" ), QgsLayerItem::LayerType::Raster, driver ) );
231 }
232 }
233 else if ( rlayer.isValid( ) )
234 {
235 // Get the identifier
236 GDALAllRegister();
237 // do not print errors, but write to debug
238 CPLPushErrorHandler( CPLQuietErrorHandler );
239 CPLErrorReset();
240 gdal::dataset_unique_ptr hDS( GDALOpen( path.toUtf8().constData(), GA_ReadOnly ) );
241 CPLPopErrorHandler();
242
243 if ( ! hDS )
244 {
245 QgsDebugMsgLevel( QStringLiteral( "GDALOpen error # %1 : %2 " ).arg( CPLGetLastErrorNo() ).arg( CPLGetLastErrorMsg() ), 2 );
246
247 }
248 else
249 {
250 QString uri( QStringLiteral( "%1:%2" ).arg( driver, path ) );
251 QString name = GDALGetMetadataItem( hDS.get(), "IDENTIFIER", nullptr );
252 hDS.reset();
253 // Fallback: will not be able to delete the table
254 if ( name.isEmpty() )
255 {
256 name = QFileInfo( path ).fileName();
257 }
258 else
259 {
260 uri += QStringLiteral( ":%1" ).arg( name );
261 }
262
263 QgsDebugMsgLevel( QStringLiteral( "Adding %1 Raster item %2 %3" ).arg( driver, name, path ), 3 );
264 children.append( new QgsOgrDbLayerInfo( path, uri, name, QString(), QStringLiteral( "Raster" ), QgsLayerItem::LayerType::Raster, driver ) );
265 }
266 }
267
268 // There were problems in reading the file: throw
269 if ( ! layer.isValid() && ! rlayer.isValid() && children.isEmpty() )
270 {
271 QString errorMessage;
272 // If it is file based and the file exists, there might be a permission error, let's change
273 // the message to give the user a hint about this possibility.
274 if ( QFile::exists( path ) )
275 {
276 errorMessage = tr( "The file does not contain any layer or there was an error opening the file.\nCheck file and directory permissions on\n%1" ).arg( QDir::toNativeSeparators( path ) );
277 }
278 else
279 {
280 errorMessage = tr( "Layer is not valid (%1)" ).arg( path );
281 }
282 throw QgsOgrLayerNotValidException( errorMessage );
283 }
284
285 return children;
286 }
287
layerName() const288 QString QgsOgrLayerItem::layerName() const
289 {
290 QFileInfo info( name() );
291 if ( info.suffix() == QLatin1String( "gz" ) )
292 return info.baseName();
293 else
294 return info.completeBaseName();
295 }
296
297 // -------
298
dataItemForLayer(QgsDataItem * parentItem,QString name,QString path,GDALDatasetH hDataSource,int layerId,bool isSubLayer,bool uniqueNames)299 static QgsOgrLayerItem *dataItemForLayer( QgsDataItem *parentItem, QString name,
300 QString path, GDALDatasetH hDataSource,
301 int layerId,
302 bool isSubLayer, bool uniqueNames )
303 {
304 OGRLayerH hLayer = GDALDatasetGetLayer( hDataSource, layerId );
305 OGRFeatureDefnH hDef = OGR_L_GetLayerDefn( hLayer );
306
307 QgsLayerItem::LayerType layerType = QgsLayerItem::Vector;
308 GDALDriverH hDriver = GDALGetDatasetDriver( hDataSource );
309 QString driverName = QString::fromUtf8( GDALGetDriverShortName( hDriver ) );
310 OGRwkbGeometryType ogrType = QgsOgrProvider::getOgrGeomType( driverName, hLayer );
311 QgsWkbTypes::Type wkbType = QgsOgrProviderUtils::qgisTypeFromOgrType( ogrType );
312 switch ( QgsWkbTypes::geometryType( wkbType ) )
313 {
314 case QgsWkbTypes::UnknownGeometry:
315 break;
316 case QgsWkbTypes::NullGeometry:
317 layerType = QgsLayerItem::TableLayer;
318 break;
319 case QgsWkbTypes::PointGeometry:
320 layerType = QgsLayerItem::Point;
321 break;
322 case QgsWkbTypes::LineGeometry:
323 layerType = QgsLayerItem::Line;
324 break;
325 case QgsWkbTypes::PolygonGeometry:
326 layerType = QgsLayerItem::Polygon;
327 break;
328 }
329
330 QgsDebugMsgLevel( QStringLiteral( "ogrType = %1 layertype = %2" ).arg( ogrType ).arg( layerType ), 2 );
331
332 QString layerUri = path;
333
334 if ( isSubLayer )
335 {
336 // we are in a collection
337 name = QString::fromUtf8( OGR_FD_GetName( hDef ) );
338 QgsDebugMsgLevel( "OGR layer name : " + name, 2 );
339 if ( !uniqueNames )
340 {
341 layerUri += "|layerid=" + QString::number( layerId );
342 }
343 else
344 {
345 layerUri += "|layername=" + name;
346 }
347 path += '/' + name;
348 }
349 Q_ASSERT( !name.isEmpty() );
350
351 QgsDebugMsgLevel( "OGR layer uri : " + layerUri, 2 );
352
353 return new QgsOgrLayerItem( parentItem, name, path, layerUri, layerType, driverName, isSubLayer );
354 }
355
356 // ----
357
QgsOgrDataCollectionItem(QgsDataItem * parent,const QString & name,const QString & path)358 QgsOgrDataCollectionItem::QgsOgrDataCollectionItem( QgsDataItem *parent, const QString &name, const QString &path )
359 : QgsDataCollectionItem( parent, name, path )
360 {
361 }
362
createChildren()363 QVector<QgsDataItem *> QgsOgrDataCollectionItem::createChildren()
364 {
365 QVector<QgsDataItem *> children;
366 QStringList skippedLayerNames;
367
368 char **papszOptions = nullptr;
369 papszOptions = CSLSetNameValue( papszOptions, "@LIST_ALL_TABLES", "YES" );
370 gdal::dataset_unique_ptr hDataSource( GDALOpenEx( mPath.toUtf8().constData(), GDAL_OF_VECTOR, nullptr, papszOptions, nullptr ) );
371 CSLDestroy( papszOptions );
372
373 GDALDriverH hDriver = GDALGetDatasetDriver( hDataSource.get() );
374 const QString driverName = QString::fromUtf8( GDALGetDriverShortName( hDriver ) );
375 if ( driverName == QLatin1String( "SQLite" ) )
376 {
377 skippedLayerNames = QgsSqliteUtils::systemTables();
378 }
379
380 if ( !hDataSource )
381 return children;
382 int numLayers = GDALDatasetGetLayerCount( hDataSource.get() );
383
384 // Check if layer names are unique, so we can use |layername= in URI
385 QMap< QString, int > mapLayerNameToCount;
386 QList< int > skippedLayers;
387 bool uniqueNames = true;
388 for ( int i = 0; i < numLayers; ++i )
389 {
390 OGRLayerH hLayer = GDALDatasetGetLayer( hDataSource.get(), i );
391 OGRFeatureDefnH hDef = OGR_L_GetLayerDefn( hLayer );
392 QString layerName = QString::fromUtf8( OGR_FD_GetName( hDef ) );
393 ++mapLayerNameToCount[layerName];
394 if ( mapLayerNameToCount[layerName] > 1 )
395 {
396 uniqueNames = false;
397 break;
398 }
399 if ( ( driverName == QLatin1String( "SQLite" ) && layerName.contains( QRegularExpression( QStringLiteral( "idx_.*_geometry($|_.*)" ) ) ) )
400 || skippedLayerNames.contains( layerName ) )
401 {
402 skippedLayers << i;
403 }
404 }
405
406 children.reserve( numLayers );
407 for ( int i = 0; i < numLayers; ++i )
408 {
409 if ( !skippedLayers.contains( i ) )
410 {
411 QgsOgrLayerItem *item = dataItemForLayer( this, QString(), mPath, hDataSource.get(), i, true, uniqueNames );
412 children.append( item );
413 }
414 }
415
416 return children;
417 }
418
saveConnection(const QString & path,const QString & ogrDriverName)419 bool QgsOgrDataCollectionItem::saveConnection( const QString &path, const QString &ogrDriverName )
420 {
421 QFileInfo fileInfo( path );
422 QString connName = fileInfo.fileName();
423 if ( ! path.isEmpty() )
424 {
425 bool ok = true;
426 while ( ok && ! QgsOgrDbConnection( connName, ogrDriverName ).path( ).isEmpty( ) )
427 {
428
429 connName = QInputDialog::getText( nullptr, tr( "Add Connection" ),
430 tr( "A connection with the same name already exists,\nplease provide a new name:" ), QLineEdit::Normal,
431 QString(), &ok );
432 }
433 if ( ok && ! connName.isEmpty() )
434 {
435 QgsOgrDbConnection connection( connName, ogrDriverName );
436 connection.setPath( path );
437 connection.save();
438 return true;
439 }
440 }
441 return false;
442 }
443
createConnection(const QString & name,const QString & extensions,const QString & ogrDriverName)444 bool QgsOgrDataCollectionItem::createConnection( const QString &name, const QString &extensions, const QString &ogrDriverName )
445 {
446 QString path = QFileDialog::getOpenFileName( nullptr, tr( "Open %1" ).arg( name ), QString(), extensions );
447 return saveConnection( path, ogrDriverName );
448 }
449
hasDragEnabled() const450 bool QgsOgrDataCollectionItem::hasDragEnabled() const
451 {
452 return true;
453 }
454
mimeUri() const455 QgsMimeDataUtils::Uri QgsOgrDataCollectionItem::mimeUri() const
456 {
457 QgsMimeDataUtils::Uri u;
458 u.providerKey = QStringLiteral( "ogr" );
459 u.uri = path();
460 u.layerType = QStringLiteral( "vector" );
461 return u;
462 }
463
databaseConnection() const464 QgsAbstractDatabaseProviderConnection *QgsOgrDataCollectionItem::databaseConnection() const
465 {
466
467 QgsAbstractDatabaseProviderConnection *conn { QgsDataCollectionItem::databaseConnection() };
468
469 // There is a chance that this is a spatialite file, but spatialite is not handled by OGR and
470 // it's not even in core.
471 if ( ! conn )
472 {
473
474 // test that file is valid with OGR
475 if ( OGRGetDriverCount() == 0 )
476 {
477 OGRRegisterAll();
478 }
479 // do not print errors, but write to debug
480 CPLPushErrorHandler( CPLQuietErrorHandler );
481 CPLErrorReset();
482 gdal::dataset_unique_ptr hDS( GDALOpenEx( path().toUtf8().constData(), GDAL_OF_VECTOR, nullptr, nullptr, nullptr ) );
483 CPLPopErrorHandler();
484
485 if ( ! hDS )
486 {
487 QgsDebugMsgLevel( QStringLiteral( "GDALOpen error # %1 : %2 on %3" ).arg( CPLGetLastErrorNo() ).arg( CPLGetLastErrorMsg() ).arg( path() ), 2 );
488 return nullptr;
489 }
490
491 GDALDriverH hDriver = GDALGetDatasetDriver( hDS.get() );
492 QString driverName = GDALGetDriverShortName( hDriver );
493
494 if ( driverName == QLatin1String( "SQLite" ) )
495 {
496 QgsProviderMetadata *md { QgsProviderRegistry::instance()->providerMetadata( QStringLiteral( "spatialite" ) ) };
497 if ( md )
498 {
499 QgsDataSourceUri uri;
500 uri.setDatabase( path( ) );
501 conn = static_cast<QgsAbstractDatabaseProviderConnection *>( md->createConnection( uri.uri(), {} ) );
502 }
503 }
504 }
505 return conn;
506 }
507
508 // ---------------------------------------------------------------------------
509
name()510 QString QgsOgrDataItemProvider::name()
511 {
512 return QStringLiteral( "OGR" );
513 }
514
dataProviderKey() const515 QString QgsOgrDataItemProvider::dataProviderKey() const
516 {
517 return QStringLiteral( "ogr" );
518 }
519
capabilities() const520 int QgsOgrDataItemProvider::capabilities() const
521 {
522 return QgsDataProvider::File | QgsDataProvider::Dir | QgsDataProvider::Net;
523 }
524
createDataItem(const QString & pathIn,QgsDataItem * parentItem)525 QgsDataItem *QgsOgrDataItemProvider::createDataItem( const QString &pathIn, QgsDataItem *parentItem )
526 {
527 QString path( pathIn );
528 if ( path.isEmpty() )
529 return nullptr;
530
531 QgsDebugMsgLevel( "thePath: " + path, 2 );
532
533 // zip settings + info
534 QgsSettings settings;
535 QString scanZipSetting = settings.value( QStringLiteral( "qgis/scanZipInBrowser2" ), "basic" ).toString();
536 QString vsiPrefix = QgsZipItem::vsiPrefix( path );
537 bool is_vsizip = ( vsiPrefix == QLatin1String( "/vsizip/" ) );
538 bool is_vsigzip = ( vsiPrefix == QLatin1String( "/vsigzip/" ) );
539 bool is_vsitar = ( vsiPrefix == QLatin1String( "/vsitar/" ) );
540
541 // should we check ext. only?
542 // check if scanItemsInBrowser2 == extension or parent dir in scanItemsFastScanUris
543 // TODO - do this in dir item, but this requires a way to inform which extensions are supported by provider
544 // maybe a callback function or in the provider registry?
545 bool scanExtSetting = false;
546 if ( ( settings.value( QStringLiteral( "qgis/scanItemsInBrowser2" ),
547 "extension" ).toString() == QLatin1String( "extension" ) ) ||
548 ( parentItem && settings.value( QStringLiteral( "qgis/scanItemsFastScanUris" ),
549 QStringList() ).toStringList().contains( parentItem->path() ) ) ||
550 ( ( is_vsizip || is_vsitar ) && parentItem && parentItem->parent() &&
551 settings.value( QStringLiteral( "qgis/scanItemsFastScanUris" ),
552 QStringList() ).toStringList().contains( parentItem->parent()->path() ) ) )
553 {
554 scanExtSetting = true;
555 }
556
557 // get suffix, removing .gz if present
558 QString tmpPath = path; //path used for testing, not for layer creation
559 if ( is_vsigzip )
560 tmpPath.chop( 3 );
561 QFileInfo info( tmpPath );
562 QString suffix = info.suffix().toLower();
563
564 // GDAL 3.1 Shapefile driver directly handles .shp.zip files
565 if ( path.endsWith( QLatin1String( ".shp.zip" ), Qt::CaseInsensitive ) &&
566 GDALIdentifyDriver( path.toUtf8().constData(), nullptr ) )
567 {
568 suffix = QStringLiteral( "shp.zip" );
569 }
570
571 // extract basename with extension
572 info.setFile( path );
573 QString name = info.fileName();
574
575 // If a .tab exists, then the corresponding .map/.dat is very likely a
576 // side-car file of the .tab
577 if ( suffix == QLatin1String( "map" ) || suffix == QLatin1String( "dat" ) )
578 {
579 if ( QFileInfo( QDir( info.path() ), info.baseName() + ".tab" ).exists() )
580 return nullptr;
581 }
582
583 QgsDebugMsgLevel( "thePath= " + path + " tmpPath= " + tmpPath + " name= " + name
584 + " suffix= " + suffix + " vsiPrefix= " + vsiPrefix, 3 );
585
586 QStringList myExtensions = QgsOgrProviderUtils::fileExtensions();
587 QStringList dirExtensions = QgsOgrProviderUtils::directoryExtensions();
588
589 // allow only normal files, supported directories, or VSIFILE items to continue
590 bool isOgrSupportedDirectory = info.isDir() && dirExtensions.contains( suffix );
591 if ( !isOgrSupportedDirectory && !info.isFile() && vsiPrefix.isEmpty() )
592 return nullptr;
593
594 // skip *.aux.xml files (GDAL auxiliary metadata files),
595 // *.shp.xml files (ESRI metadata) and *.tif.xml files (TIFF metadata)
596 // unless that extension is in the list (*.xml might be though)
597 if ( path.endsWith( QLatin1String( ".aux.xml" ), Qt::CaseInsensitive ) &&
598 !myExtensions.contains( QStringLiteral( "aux.xml" ) ) )
599 return nullptr;
600 if ( path.endsWith( QLatin1String( ".shp.xml" ), Qt::CaseInsensitive ) &&
601 !myExtensions.contains( QStringLiteral( "shp.xml" ) ) )
602 return nullptr;
603 if ( path.endsWith( QLatin1String( ".tif.xml" ), Qt::CaseInsensitive ) &&
604 !myExtensions.contains( QStringLiteral( "tif.xml" ) ) )
605 return nullptr;
606
607 // skip QGIS style xml files
608 if ( path.endsWith( QLatin1String( ".xml" ), Qt::CaseInsensitive ) &&
609 QgsStyle::isXmlStyleFile( path ) )
610 return nullptr;
611
612 // We have to filter by extensions, otherwise e.g. all Shapefile files are displayed
613 // because OGR drive can open also .dbf, .shx.
614 if ( myExtensions.indexOf( suffix ) < 0 && !dirExtensions.contains( suffix ) )
615 {
616 bool matches = false;
617 const auto constWildcards = QgsOgrProviderUtils::wildcards();
618 for ( const QString &wildcard : constWildcards )
619 {
620 QRegExp rx( wildcard, Qt::CaseInsensitive, QRegExp::Wildcard );
621 if ( rx.exactMatch( info.fileName() ) )
622 {
623 matches = true;
624 break;
625 }
626 }
627 if ( !matches )
628 return nullptr;
629 }
630
631 // .dbf should probably appear if .shp is not present
632 if ( suffix == QLatin1String( "dbf" ) )
633 {
634 QString pathShp = path.left( path.count() - 4 ) + ".shp";
635 if ( QFileInfo::exists( pathShp ) )
636 return nullptr;
637 }
638
639 // fix vsifile path and name
640 if ( !vsiPrefix.isEmpty() )
641 {
642 // add vsiPrefix to path if needed
643 if ( !path.startsWith( vsiPrefix ) )
644 path = vsiPrefix + path;
645 // if this is a /vsigzip/path_to_zip.zip/file_inside_zip remove the full path from the name
646 // no need to change the name I believe
647 #if 0
648 if ( ( is_vsizip || is_vsitar ) && ( path != vsiPrefix + parentItem->path() ) )
649 {
650 name = path;
651 name = name.replace( vsiPrefix + parentItem->path() + '/', "" );
652 }
653 #endif
654 }
655
656 // Filters out the OGR/GDAL supported formats that can contain multiple layers
657 // and should be treated like a DB: GeoPackage and SQLite
658 // NOTE: this formats are scanned for rasters too and they must
659 // be skipped by "gdal" provider or the rasters will be listed
660 // twice. ogrSupportedDbLayersExtensions must be kept in sync
661 // with the companion variable (same name) in the gdal provider
662 // class
663 // TODO: add more OGR supported multiple layers formats here!
664 static QStringList sOgrSupportedDbLayersExtensions { QStringLiteral( "gpkg" ),
665 QStringLiteral( "sqlite" ),
666 QStringLiteral( "db" ),
667 QStringLiteral( "gdb" ),
668 QStringLiteral( "kml" ),
669 QStringLiteral( "osm" ),
670 QStringLiteral( "mdb" ),
671 QStringLiteral( "accdb" ),
672 QStringLiteral( "xls" ),
673 QStringLiteral( "xlsx" ),
674 QStringLiteral( "gpx" ),
675 QStringLiteral( "pdf" ),
676 QStringLiteral( "pbf" ) };
677 static QStringList sOgrSupportedDbDriverNames { QStringLiteral( "GPKG" ),
678 QStringLiteral( "db" ),
679 QStringLiteral( "gdb" ),
680 QStringLiteral( "xlsx" ),
681 QStringLiteral( "xls" ),
682 QStringLiteral( "pgdb" )};
683
684 // these extensions are trivial to read, so there's no need to rely on
685 // the extension only scan here -- avoiding it always gives us the correct data type
686 // and sublayer visibility
687 static QStringList sSkipFastTrackExtensions { QStringLiteral( "xlsx" ),
688 QStringLiteral( "ods" ),
689 QStringLiteral( "csv" ),
690 QStringLiteral( "nc" ),
691 QStringLiteral( "shp.zip" ) };
692
693 // Fast track: return item without testing if:
694 // scanExtSetting or zipfile and scan zip == "Basic scan"
695 // netCDF files can be both raster or vector, so fallback to opening
696 if ( ( scanExtSetting ||
697 ( ( is_vsizip || is_vsitar ) && scanZipSetting == QLatin1String( "basic" ) ) ) &&
698 !sSkipFastTrackExtensions.contains( suffix ) )
699 {
700 // if this is a VRT file make sure it is vector VRT to avoid duplicates
701 if ( suffix == QLatin1String( "vrt" ) )
702 {
703 CPLPushErrorHandler( CPLQuietErrorHandler );
704 CPLErrorReset();
705 GDALDriverH hDriver = GDALIdentifyDriver( path.toUtf8().constData(), nullptr );
706 CPLPopErrorHandler();
707 if ( !hDriver || GDALGetDriverShortName( hDriver ) == QLatin1String( "VRT" ) )
708 {
709 QgsDebugMsgLevel( QStringLiteral( "Skipping VRT file because root is not a OGR VRT" ), 2 );
710 return nullptr;
711 }
712 }
713 // Handle collections
714 // Check if the layer has sublayers by comparing the extension
715 QgsDataItem *item = nullptr;
716 if ( ! sOgrSupportedDbLayersExtensions.contains( suffix ) )
717 {
718 item = new QgsOgrLayerItem( parentItem, name, path, path, QgsLayerItem::Vector );
719 }
720 else if ( suffix.compare( QLatin1String( "gpkg" ), Qt::CaseInsensitive ) == 0 )
721 {
722 item = new QgsGeoPackageCollectionItem( parentItem, name, path );
723 }
724 else
725 {
726 item = new QgsOgrDataCollectionItem( parentItem, name, path );
727 }
728
729 if ( item )
730 return item;
731 }
732
733 // Slow track: scan file contents
734 QgsDataItem *item = nullptr;
735
736 // test that file is valid with OGR
737 if ( OGRGetDriverCount() == 0 )
738 {
739 OGRRegisterAll();
740 }
741 // do not print errors, but write to debug
742 CPLPushErrorHandler( CPLQuietErrorHandler );
743 CPLErrorReset();
744 gdal::dataset_unique_ptr hDS( GDALOpenEx( path.toUtf8().constData(), GDAL_OF_VECTOR, nullptr, nullptr, nullptr ) );
745 CPLPopErrorHandler();
746
747 if ( ! hDS )
748 {
749 QgsDebugMsgLevel( QStringLiteral( "GDALOpen error # %1 : %2 on %3" ).arg( CPLGetLastErrorNo() ).arg( CPLGetLastErrorMsg() ).arg( path ), 2 );
750 return nullptr;
751 }
752
753 GDALDriverH hDriver = GDALGetDatasetDriver( hDS.get() );
754 QString driverName = GDALGetDriverShortName( hDriver );
755 QgsDebugMsgLevel( QStringLiteral( "GDAL Driver : %1" ).arg( driverName ), 2 );
756 int numLayers = GDALDatasetGetLayerCount( hDS.get() );
757
758 // GeoPackage needs a specialized data item, mainly because of raster deletion not
759 // yet implemented in GDAL (2.2.1)
760 if ( driverName == QLatin1String( "GPKG" ) )
761 {
762 item = new QgsGeoPackageCollectionItem( parentItem, name, path );
763 }
764 else if ( numLayers > 1 || sOgrSupportedDbDriverNames.contains( driverName ) )
765 {
766 item = new QgsOgrDataCollectionItem( parentItem, name, path );
767 }
768 else
769 {
770 item = dataItemForLayer( parentItem, name, path, hDS.get(), 0, false, true );
771 }
772 return item;
773 }
774
handlesDirectoryPath(const QString & path)775 bool QgsOgrDataItemProvider::handlesDirectoryPath( const QString &path )
776 {
777 QFileInfo info( path );
778 QString suffix = info.suffix().toLower();
779
780 QStringList dirExtensions = QgsOgrProviderUtils::directoryExtensions();
781 return dirExtensions.contains( suffix );
782 }
783
784 ///@endcond
785