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