LCOV - code coverage report
Current view: top level - core/providers/ogr - qgsogrdataitems.cpp (source / functions) Hit Total Coverage
Test: coverage.info.cleaned Lines: 4 444 0.9 %
Date: 2021-04-10 08:29:14 Functions: 0 0 -
Branches: 0 0 -

           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

Generated by: LCOV version 1.14