Branch data Line data Source code
1 : : /***************************************************************************
2 : : qgsgdaldataitems.cpp
3 : : ---------------------
4 : : begin : October 2011
5 : : copyright : (C) 2011 by Martin Dobias
6 : : email : wonder dot sk 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 "qgsgdaldataitems.h"
17 : :
18 : : ///@cond PRIVATE
19 : : #include "qgsgdalprovider.h"
20 : : #include "qgslogger.h"
21 : : #include "qgsmbtiles.h"
22 : : #include "qgssettings.h"
23 : : #include "qgsogrutils.h"
24 : : #include "qgsproject.h"
25 : : #include "qgsgdalutils.h"
26 : : #include "qgsvectortiledataitems.h"
27 : : #include "qgsproviderregistry.h"
28 : : #include "symbology/qgsstyle.h"
29 : :
30 : : #include <QFileInfo>
31 : : #include <QAction>
32 : : #include <mutex>
33 : : #include <QMessageBox>
34 : : #include <QUrlQuery>
35 : : #include <QUrl>
36 : :
37 : : // defined in qgsgdalprovider.cpp
38 : : void buildSupportedRasterFileFilterAndExtensions( QString &fileFiltersString, QStringList &extensions, QStringList &wildcards );
39 : :
40 : :
41 : 0 : QgsGdalLayerItem::QgsGdalLayerItem( QgsDataItem *parent,
42 : : const QString &name, const QString &path, const QString &uri,
43 : : QStringList *sublayers )
44 : 0 : : QgsLayerItem( parent, name, path, uri, QgsLayerItem::Raster, QStringLiteral( "gdal" ) )
45 : 0 : {
46 : 0 : mToolTip = uri;
47 : : // save sublayers for subsequent access
48 : : // if there are sublayers, set populated=false so item can be populated on demand
49 : 0 : if ( sublayers && !sublayers->isEmpty() )
50 : : {
51 : 0 : mSublayers = *sublayers;
52 : : // We have sublayers: we are able to create children!
53 : 0 : mCapabilities |= Fertile;
54 : 0 : setState( NotPopulated );
55 : 0 : }
56 : : else
57 : 0 : setState( Populated );
58 : 0 : }
59 : :
60 : :
61 : 0 : bool QgsGdalLayerItem::setCrs( const QgsCoordinateReferenceSystem &crs )
62 : : {
63 : 0 : gdal::dataset_unique_ptr hDS( GDALOpen( mPath.toUtf8().constData(), GA_Update ) );
64 : 0 : if ( !hDS )
65 : 0 : return false;
66 : :
67 : 0 : QString wkt = crs.toWkt( QgsCoordinateReferenceSystem::WKT_PREFERRED_GDAL );
68 : 0 : if ( GDALSetProjection( hDS.get(), wkt.toLocal8Bit().data() ) != CE_None )
69 : : {
70 : 0 : QgsDebugMsg( QStringLiteral( "Could not set CRS" ) );
71 : 0 : return false;
72 : : }
73 : :
74 : 0 : return true;
75 : 0 : }
76 : :
77 : 0 : QVector<QgsDataItem *> QgsGdalLayerItem::createChildren()
78 : : {
79 : 0 : QgsDebugMsgLevel( "Entered, path=" + path(), 3 );
80 : 0 : QVector<QgsDataItem *> children;
81 : :
82 : : // get children from sublayers
83 : 0 : if ( !mSublayers.isEmpty() )
84 : : {
85 : 0 : QgsDataItem *childItem = nullptr;
86 : 0 : QgsDebugMsgLevel( QStringLiteral( "got %1 sublayers" ).arg( mSublayers.count() ), 3 );
87 : 0 : for ( int i = 0; i < mSublayers.count(); i++ )
88 : : {
89 : 0 : const QStringList parts = mSublayers[i].split( QgsDataProvider::sublayerSeparator() );
90 : 0 : const QString path = parts[0];
91 : 0 : const QString desc = parts[1];
92 : 0 : childItem = new QgsGdalLayerItem( this, desc, path, path );
93 : 0 : if ( childItem )
94 : : {
95 : 0 : children.append( childItem );
96 : 0 : }
97 : 0 : }
98 : 0 : }
99 : :
100 : 0 : return children;
101 : 0 : }
102 : :
103 : 0 : QString QgsGdalLayerItem::layerName() const
104 : : {
105 : 0 : QFileInfo info( name() );
106 : 0 : if ( info.suffix() == QLatin1String( "gz" ) )
107 : 0 : return info.baseName();
108 : : else
109 : 0 : return info.completeBaseName();
110 : 0 : }
111 : :
112 : : // ---------------------------------------------------------------------------
113 : :
114 : 0 : QString QgsGdalDataItemProvider::name()
115 : : {
116 : 0 : return QStringLiteral( "GDAL" );
117 : : }
118 : :
119 : 0 : int QgsGdalDataItemProvider::capabilities() const
120 : : {
121 : 0 : return QgsDataProvider::File | QgsDataProvider::Dir | QgsDataProvider::Net;
122 : : }
123 : :
124 : 0 : QgsDataItem *QgsGdalDataItemProvider::createDataItem( const QString &pathIn, QgsDataItem *parentItem )
125 : : {
126 : 0 : static QString sFilterString;
127 : 0 : static QStringList sExtensions = QStringList();
128 : 0 : static QStringList sWildcards = QStringList();
129 : 0 : static QMutex sBuildingFilters;
130 : :
131 : 0 : QString path( pathIn );
132 : 0 : if ( path.isEmpty() )
133 : 0 : return nullptr;
134 : :
135 : : // if another provider has preference for this path, let it win. This allows us to hide known files
136 : : // more strongly associated with another provider from showing duplicate entries for the ogr provider.
137 : : // e.g. in particular this hides "ept.json" files from showing as a non-functional ogr data item, and
138 : : // instead ONLY shows them as the functional EPT point cloud provider items
139 : 0 : if ( QgsProviderRegistry::instance()->shouldDeferUriForOtherProviders( path, QStringLiteral( "gdal" ) ) )
140 : : {
141 : 0 : return nullptr;
142 : : }
143 : :
144 : : // hide blocklisted URIs, such as .aux.xml files
145 : 0 : if ( QgsProviderRegistry::instance()->uriIsBlocklisted( path ) )
146 : 0 : return nullptr;
147 : :
148 : 0 : QgsDebugMsgLevel( "thePath = " + path, 2 );
149 : :
150 : : // zip settings + info
151 : 0 : QgsSettings settings;
152 : 0 : QString scanZipSetting = settings.value( QStringLiteral( "qgis/scanZipInBrowser2" ), "basic" ).toString();
153 : 0 : QString vsiPrefix = QgsZipItem::vsiPrefix( path );
154 : 0 : bool is_vsizip = ( vsiPrefix == QLatin1String( "/vsizip/" ) );
155 : 0 : bool is_vsigzip = ( vsiPrefix == QLatin1String( "/vsigzip/" ) );
156 : 0 : bool is_vsitar = ( vsiPrefix == QLatin1String( "/vsitar/" ) );
157 : :
158 : : // should we check ext. only?
159 : : // check if scanItemsInBrowser2 == extension or parent dir in scanItemsFastScanUris
160 : : // TODO - do this in dir item, but this requires a way to inform which extensions are supported by provider
161 : : // maybe a callback function or in the provider registry?
162 : 0 : bool scanExtSetting = false;
163 : 0 : if ( ( settings.value( QStringLiteral( "qgis/scanItemsInBrowser2" ),
164 : 0 : "extension" ).toString() == QLatin1String( "extension" ) ) ||
165 : 0 : ( parentItem && settings.value( QStringLiteral( "qgis/scanItemsFastScanUris" ),
166 : 0 : QStringList() ).toStringList().contains( parentItem->path() ) ) ||
167 : 0 : ( ( is_vsizip || is_vsitar ) && parentItem && parentItem->parent() &&
168 : 0 : settings.value( QStringLiteral( "qgis/scanItemsFastScanUris" ),
169 : 0 : QStringList() ).toStringList().contains( parentItem->parent()->path() ) ) )
170 : : {
171 : 0 : scanExtSetting = true;
172 : 0 : }
173 : :
174 : 0 : if ( path.endsWith( QLatin1String( ".shp.zip" ), Qt::CaseInsensitive ) )
175 : : {
176 : : // .shp.zip are vector datasets
177 : 0 : return nullptr;
178 : : }
179 : :
180 : : // get suffix, removing .gz if present
181 : 0 : QString tmpPath = path; //path used for testing, not for layer creation
182 : 0 : if ( is_vsigzip )
183 : 0 : tmpPath.chop( 3 );
184 : 0 : QFileInfo info( tmpPath );
185 : 0 : QString suffix = info.suffix().toLower();
186 : :
187 : 0 : if ( suffix == QLatin1String( "txt" ) )
188 : : {
189 : : // never ever show .txt files as datasets in browser -- they are only used for geospatial data in extremely rare cases
190 : : // and are predominantly just noise in the browser
191 : 0 : return nullptr;
192 : : }
193 : :
194 : : // extract basename with extension
195 : 0 : info.setFile( path );
196 : 0 : QString name = info.fileName();
197 : :
198 : : // If a .tab exists, then the corresponding .map/.dat is very likely a
199 : : // side-car file of the .tab
200 : 0 : if ( suffix == QLatin1String( "map" ) || suffix == QLatin1String( "dat" ) )
201 : : {
202 : 0 : if ( QFileInfo( QDir( info.path() ), info.baseName() + ".tab" ).exists() )
203 : 0 : return nullptr;
204 : 0 : }
205 : :
206 : 0 : QgsDebugMsgLevel( "path= " + path + " tmpPath= " + tmpPath + " name= " + name
207 : : + " suffix= " + suffix + " vsiPrefix= " + vsiPrefix, 3 );
208 : :
209 : : // allow only normal files or VSIFILE items to continue
210 : 0 : if ( !info.isFile() && vsiPrefix.isEmpty() )
211 : 0 : return nullptr;
212 : :
213 : : // get supported extensions
214 : : static std::once_flag initialized;
215 : 0 : std::call_once( initialized, [ = ]
216 : : {
217 : 0 : buildSupportedRasterFileFilterAndExtensions( sFilterString, sExtensions, sWildcards );
218 : 0 : QgsDebugMsgLevel( QStringLiteral( "extensions: " ) + sExtensions.join( ' ' ), 2 );
219 : 0 : QgsDebugMsgLevel( QStringLiteral( "wildcards: " ) + sWildcards.join( ' ' ), 2 );
220 : 0 : } );
221 : :
222 : : // skip QGIS style xml files
223 : 0 : if ( path.endsWith( QLatin1String( ".xml" ), Qt::CaseInsensitive ) &&
224 : 0 : QgsStyle::isXmlStyleFile( path ) )
225 : 0 : return nullptr;
226 : :
227 : : // Filter files by extension
228 : 0 : if ( !sExtensions.contains( suffix ) )
229 : : {
230 : 0 : bool matches = false;
231 : 0 : const auto constSWildcards = sWildcards;
232 : 0 : for ( const QString &wildcard : constSWildcards )
233 : : {
234 : 0 : QRegExp rx( wildcard, Qt::CaseInsensitive, QRegExp::Wildcard );
235 : 0 : if ( rx.exactMatch( info.fileName() ) )
236 : : {
237 : 0 : matches = true;
238 : 0 : break;
239 : : }
240 : 0 : }
241 : 0 : if ( !matches )
242 : 0 : return nullptr;
243 : 0 : }
244 : :
245 : : // fix vsifile path and name
246 : 0 : if ( !vsiPrefix.isEmpty() )
247 : : {
248 : : // add vsiPrefix to path if needed
249 : 0 : if ( !path.startsWith( vsiPrefix ) )
250 : 0 : path = vsiPrefix + path;
251 : : // if this is a /vsigzip/path_to_zip.zip/file_inside_zip remove the full path from the name
252 : : // no need to change the name I believe
253 : : #if 0
254 : : if ( ( is_vsizip || is_vsitar ) && ( path != vsiPrefix + parentItem->path() ) )
255 : : {
256 : : name = path;
257 : : name = name.replace( vsiPrefix + parentItem->path() + '/', "" );
258 : : }
259 : : #endif
260 : 0 : }
261 : :
262 : 0 : if ( suffix == QLatin1String( "mbtiles" ) )
263 : : {
264 : 0 : QgsMbTiles reader( path );
265 : 0 : if ( reader.open() )
266 : : {
267 : 0 : if ( reader.metadataValue( "format" ) == QLatin1String( "pbf" ) )
268 : : {
269 : : // these are vector tiles
270 : 0 : QUrlQuery uq;
271 : 0 : uq.addQueryItem( QStringLiteral( "type" ), QStringLiteral( "mbtiles" ) );
272 : 0 : uq.addQueryItem( QStringLiteral( "url" ), path );
273 : 0 : QString encodedUri = uq.toString();
274 : 0 : return new QgsVectorTileLayerItem( parentItem, name, path, encodedUri );
275 : 0 : }
276 : : else
277 : : {
278 : : // handled by WMS provider
279 : 0 : QUrlQuery uq;
280 : 0 : uq.addQueryItem( QStringLiteral( "type" ), QStringLiteral( "mbtiles" ) );
281 : 0 : uq.addQueryItem( QStringLiteral( "url" ), QUrl::fromLocalFile( path ).toString() );
282 : 0 : QString encodedUri = uq.toString();
283 : 0 : QgsLayerItem *item = new QgsLayerItem( parentItem, name, path, encodedUri, QgsLayerItem::Raster, QStringLiteral( "wms" ) );
284 : 0 : item->setState( QgsDataItem::Populated );
285 : 0 : return item;
286 : 0 : }
287 : : }
288 : 0 : }
289 : :
290 : : // Filters out the OGR/GDAL supported formats that can contain multiple layers
291 : : // and should be treated like a DB: GeoPackage and SQLite
292 : : // NOTE: this formats are scanned for rasters too and they are handled
293 : : // by the "ogr" provider. For this reason they must
294 : : // be skipped by "gdal" provider or the rasters will be listed
295 : : // twice. ogrSupportedDbLayersExtensions must be kept in sync
296 : : // with the companion variable (same name) in the ogr provider
297 : : // class
298 : : // TODO: add more OGR supported multiple layers formats here!
299 : 0 : QStringList ogrSupportedDbLayersExtensions;
300 : 0 : ogrSupportedDbLayersExtensions << QStringLiteral( "gpkg" ) << QStringLiteral( "sqlite" ) << QStringLiteral( "db" ) << QStringLiteral( "gdb" );
301 : 0 : QStringList ogrSupportedDbDriverNames;
302 : 0 : ogrSupportedDbDriverNames << QStringLiteral( "GPKG" ) << QStringLiteral( "db" ) << QStringLiteral( "gdb" );
303 : :
304 : : // return item without testing if:
305 : : // scanExtSetting
306 : : // or zipfile and scan zip == "Basic scan"
307 : 0 : if ( ( scanExtSetting ||
308 : 0 : ( ( is_vsizip || is_vsitar ) && scanZipSetting == QLatin1String( "basic" ) ) ) &&
309 : 0 : suffix != QLatin1String( "nc" ) )
310 : : {
311 : : // Skip this layer if it's handled by ogr:
312 : 0 : if ( ogrSupportedDbLayersExtensions.contains( suffix ) )
313 : : {
314 : 0 : return nullptr;
315 : : }
316 : :
317 : : // if this is a VRT file make sure it is raster VRT to avoid duplicates
318 : 0 : if ( suffix == QLatin1String( "vrt" ) )
319 : : {
320 : : // do not print errors, but write to debug
321 : 0 : CPLPushErrorHandler( CPLQuietErrorHandler );
322 : 0 : CPLErrorReset();
323 : 0 : GDALDriverH hDriver = GDALIdentifyDriver( path.toUtf8().constData(), nullptr );
324 : 0 : CPLPopErrorHandler();
325 : 0 : if ( !hDriver || GDALGetDriverShortName( hDriver ) == QLatin1String( "OGR_VRT" ) )
326 : : {
327 : 0 : QgsDebugMsgLevel( QStringLiteral( "Skipping VRT file because root is not a GDAL VRT" ), 2 );
328 : 0 : return nullptr;
329 : : }
330 : 0 : }
331 : : // add the item
332 : 0 : QStringList sublayers;
333 : 0 : QgsDebugMsgLevel( QStringLiteral( "adding item name=%1 path=%2" ).arg( name, path ), 2 );
334 : 0 : QgsLayerItem *item = new QgsGdalLayerItem( parentItem, name, path, path, &sublayers );
335 : 0 : if ( item )
336 : 0 : return item;
337 : 0 : }
338 : :
339 : : // test that file is valid with GDAL
340 : 0 : GDALAllRegister();
341 : : // do not print errors, but write to debug
342 : 0 : CPLPushErrorHandler( CPLQuietErrorHandler );
343 : 0 : CPLErrorReset();
344 : 0 : gdal::dataset_unique_ptr hDS( GDALOpen( path.toUtf8().constData(), GA_ReadOnly ) );
345 : 0 : CPLPopErrorHandler();
346 : :
347 : 0 : if ( ! hDS )
348 : : {
349 : 0 : QgsDebugMsg( QStringLiteral( "GDALOpen error # %1 : %2 " ).arg( CPLGetLastErrorNo() ).arg( CPLGetLastErrorMsg() ) );
350 : 0 : return nullptr;
351 : : }
352 : :
353 : 0 : GDALDriverH hDriver = GDALGetDatasetDriver( hDS.get() );
354 : 0 : QString ogrDriverName = GDALGetDriverShortName( hDriver );
355 : :
356 : : // Skip this layer if it's handled by ogr:
357 : 0 : if ( ogrSupportedDbDriverNames.contains( ogrDriverName ) )
358 : : {
359 : 0 : return nullptr;
360 : : }
361 : :
362 : 0 : QStringList sublayers = QgsGdalProvider::subLayers( hDS.get() );
363 : 0 : hDS.reset();
364 : :
365 : 0 : QgsDebugMsgLevel( "GdalDataset opened " + path, 2 );
366 : :
367 : 0 : QgsLayerItem *item = new QgsGdalLayerItem( parentItem, name, path, path,
368 : : &sublayers );
369 : :
370 : 0 : return item;
371 : 0 : }
372 : :
373 : : ///@endcond
|