diff --git a/python/core/auto_generated/mesh/qgsmeshlayer.sip.in b/python/core/auto_generated/mesh/qgsmeshlayer.sip.in index 50e1e937d53..52ab4769e0a 100644 --- a/python/core/auto_generated/mesh/qgsmeshlayer.sip.in +++ b/python/core/auto_generated/mesh/qgsmeshlayer.sip.in @@ -88,6 +88,8 @@ Constructor for LayerOptions with optional ``transformContext``. %End QgsCoordinateTransformContext transformContext; + + bool allowInvalidCrs; }; explicit QgsMeshLayer( const QString &path = QString(), const QString &baseName = QString(), const QString &providerLib = QStringLiteral( "mesh_memory" ), diff --git a/python/core/auto_generated/qgsmaplayer.sip.in b/python/core/auto_generated/qgsmaplayer.sip.in index 7dabfa8ede7..614803f0b4a 100644 --- a/python/core/auto_generated/qgsmaplayer.sip.in +++ b/python/core/auto_generated/qgsmaplayer.sip.in @@ -1679,6 +1679,7 @@ this method is now deprecated and always return false, because circular dependen + }; QFlags operator|(QgsMapLayer::LayerFlag f1, QFlags f2); diff --git a/python/core/auto_generated/qgsvectorlayer.sip.in b/python/core/auto_generated/qgsvectorlayer.sip.in index e00fdd36ba2..5eaefd4497e 100644 --- a/python/core/auto_generated/qgsvectorlayer.sip.in +++ b/python/core/auto_generated/qgsvectorlayer.sip.in @@ -352,6 +352,8 @@ Constructor for LayerOptions. QgsCoordinateReferenceSystem fallbackCrs; + bool allowInvalidCrs; + }; explicit QgsVectorLayer( const QString &path = QString(), const QString &baseName = QString(), diff --git a/python/core/auto_generated/raster/qgsrasterlayer.sip.in b/python/core/auto_generated/raster/qgsrasterlayer.sip.in index 22b8ffa9a90..d7875d82d22 100644 --- a/python/core/auto_generated/raster/qgsrasterlayer.sip.in +++ b/python/core/auto_generated/raster/qgsrasterlayer.sip.in @@ -114,6 +114,8 @@ Constructor for LayerOptions. QgsCoordinateTransformContext transformContext; + bool allowInvalidCrs; + }; explicit QgsRasterLayer( const QString &uri, diff --git a/python/plugins/processing/tools/dataobjects.py b/python/plugins/processing/tools/dataobjects.py index 3348311d0d7..4b7fe10e4fb 100644 --- a/python/plugins/processing/tools/dataobjects.py +++ b/python/plugins/processing/tools/dataobjects.py @@ -113,16 +113,14 @@ def load(fileName, name=None, crs=None, style=None, isRaster=False): if fileName is None: return - prjSetting = None - settings = QgsSettings() - if crs is not None: - prjSetting = settings.value('/Projections/defaultBehavior') - settings.setValue('/Projections/defaultBehavior', '') + if name is None: name = os.path.split(fileName)[1] if isRaster: - qgslayer = QgsRasterLayer(fileName, name) + options = QgsRasterLayer.LayerOptions() + options.allowInvalidCrs = True + qgslayer = QgsRasterLayer(fileName, name, 'gdal', options) if qgslayer.isValid(): if crs is not None and qgslayer.crs() is None: qgslayer.setCrs(crs, False) @@ -131,13 +129,13 @@ def load(fileName, name=None, crs=None, style=None, isRaster=False): qgslayer.loadNamedStyle(style) QgsProject.instance().addMapLayers([qgslayer]) else: - if prjSetting: - settings.setValue('/Projections/defaultBehavior', prjSetting) raise RuntimeError(QCoreApplication.translate('dataobject', 'Could not load layer: {0}\nCheck the processing framework log to look for errors.').format( fileName)) else: - qgslayer = QgsVectorLayer(fileName, name, 'ogr') + options = QgsVectorLayer.LayerOptions() + options.allowInvalidCrs = True + qgslayer = QgsVectorLayer(fileName, name, 'ogr', options) if qgslayer.isValid(): if crs is not None and qgslayer.crs() is None: qgslayer.setCrs(crs, False) @@ -151,9 +149,6 @@ def load(fileName, name=None, crs=None, style=None, isRaster=False): qgslayer.loadNamedStyle(style) QgsProject.instance().addMapLayers([qgslayer]) - if prjSetting: - settings.setValue('/Projections/defaultBehavior', prjSetting) - return qgslayer diff --git a/src/core/mesh/qgsmeshlayer.cpp b/src/core/mesh/qgsmeshlayer.cpp index 8823e0174f4..792d4bcff77 100644 --- a/src/core/mesh/qgsmeshlayer.cpp +++ b/src/core/mesh/qgsmeshlayer.cpp @@ -42,6 +42,8 @@ QgsMeshLayer::QgsMeshLayer( const QString &meshLayerPath, const QgsMeshLayer::LayerOptions &options ) : QgsMapLayer( QgsMapLayerType::MeshLayer, baseName, meshLayerPath ) { + mShouldValidateCrs = !options.allowInvalidCrs; + setProviderType( providerKey ); // if we’re given a provider type, try to create and bind one to this layer if ( !meshLayerPath.isEmpty() && !providerKey.isEmpty() ) diff --git a/src/core/mesh/qgsmeshlayer.h b/src/core/mesh/qgsmeshlayer.h index 7ff07d22626..69519b9a2bd 100644 --- a/src/core/mesh/qgsmeshlayer.h +++ b/src/core/mesh/qgsmeshlayer.h @@ -107,6 +107,21 @@ class CORE_EXPORT QgsMeshLayer : public QgsMapLayer {} QgsCoordinateTransformContext transformContext; + + /** + * Controls whether the layer is allowed to have an invalid/unknown CRS. + * + * If TRUE, then no validation will be performed on the layer's CRS and the layer + * layer's crs() may be invalid() (i.e. the layer will have no georeferencing available + * and will be treated as having purely numerical coordinates). + * + * If FALSE (the default), the layer's CRS will be validated using QgsCoordinateReferenceSystem::validate(), + * which may cause a blocking, user-facing dialog asking users to manually select the correct CRS for the + * layer. + * + * \since QGIS 3.10 + */ + bool allowInvalidCrs = false; }; /** diff --git a/src/core/processing/qgsprocessingutils.cpp b/src/core/processing/qgsprocessingutils.cpp index 3781d6e7a46..60853357269 100644 --- a/src/core/processing/qgsprocessingutils.cpp +++ b/src/core/processing/qgsprocessingutils.cpp @@ -193,28 +193,6 @@ QgsMapLayer *QgsProcessingUtils::mapLayerFromStore( const QString &string, QgsMa return nullptr; } -///@cond PRIVATE -class ProjectionSettingRestorer -{ - public: - - ProjectionSettingRestorer() - { - QgsSettings settings; - previousSetting = settings.value( QStringLiteral( "/Projections/defaultBehavior" ) ).toString(); - settings.setValue( QStringLiteral( "/Projections/defaultBehavior" ), QStringLiteral( "useProject" ) ); - } - - ~ProjectionSettingRestorer() - { - QgsSettings settings; - settings.setValue( QStringLiteral( "/Projections/defaultBehavior" ), previousSetting ); - } - - QString previousSetting; -}; -///@endcond PRIVATE - QgsMapLayer *QgsProcessingUtils::loadMapLayerFromString( const QString &string, const QgsCoordinateTransformContext &transformContext, LayerHint typeHint ) { QStringList components = string.split( '|' ); @@ -229,10 +207,6 @@ QgsMapLayer *QgsProcessingUtils::loadMapLayerFromString( const QString &string, else return nullptr; - // TODO - remove when there is a cleaner way to block the unknown projection dialog! - ProjectionSettingRestorer restorer; - ( void )restorer; // no warnings - QString name = fi.baseName(); // brute force attempt to load a matching layer @@ -240,6 +214,7 @@ QgsMapLayer *QgsProcessingUtils::loadMapLayerFromString( const QString &string, { QgsVectorLayer::LayerOptions options { transformContext }; options.loadDefaultStyle = false; + options.allowInvalidCrs = true; std::unique_ptr< QgsVectorLayer > layer = qgis::make_unique( string, name, QStringLiteral( "ogr" ), options ); if ( layer->isValid() ) { @@ -250,6 +225,7 @@ QgsMapLayer *QgsProcessingUtils::loadMapLayerFromString( const QString &string, { QgsRasterLayer::LayerOptions rasterOptions; rasterOptions.loadDefaultStyle = false; + rasterOptions.allowInvalidCrs = true; std::unique_ptr< QgsRasterLayer > rasterLayer( new QgsRasterLayer( string, name, QStringLiteral( "gdal" ), rasterOptions ) ); if ( rasterLayer->isValid() ) { @@ -259,6 +235,7 @@ QgsMapLayer *QgsProcessingUtils::loadMapLayerFromString( const QString &string, if ( typeHint == LayerHint::UnknownType || typeHint == LayerHint::Mesh ) { QgsMeshLayer::LayerOptions meshOptions; + meshOptions.allowInvalidCrs = true; std::unique_ptr< QgsMeshLayer > meshLayer( new QgsMeshLayer( string, name, QStringLiteral( "mdal" ), meshOptions ) ); if ( meshLayer->isValid() ) { diff --git a/src/core/providers/memory/qgsmemoryproviderutils.cpp b/src/core/providers/memory/qgsmemoryproviderutils.cpp index 947829d81b6..cf58bd921fd 100644 --- a/src/core/providers/memory/qgsmemoryproviderutils.cpp +++ b/src/core/providers/memory/qgsmemoryproviderutils.cpp @@ -74,5 +74,7 @@ QgsVectorLayer *QgsMemoryProviderUtils::createMemoryLayer( const QString &name, } QString uri = geomType + '?' + parts.join( '&' ); - return new QgsVectorLayer( uri, name, QStringLiteral( "memory" ), QgsVectorLayer::LayerOptions( QgsCoordinateTransformContext() ) ); + QgsVectorLayer::LayerOptions options{ QgsCoordinateTransformContext() }; + options.allowInvalidCrs = true; + return new QgsVectorLayer( uri, name, QStringLiteral( "memory" ), options ); } diff --git a/src/core/qgsmaplayer.cpp b/src/core/qgsmaplayer.cpp index a210e1efae7..e8946f597b3 100644 --- a/src/core/qgsmaplayer.cpp +++ b/src/core/qgsmaplayer.cpp @@ -120,6 +120,7 @@ void QgsMapLayer::clone( QgsMapLayer *layer ) const layer->setLegendUrl( legendUrl() ); layer->setLegendUrlFormat( legendUrlFormat() ); layer->setDependencies( dependencies() ); + layer->mShouldValidateCrs = mShouldValidateCrs; layer->setCrs( crs() ); layer->setCustomProperties( mCustomProperties ); } @@ -764,7 +765,7 @@ void QgsMapLayer::setCrs( const QgsCoordinateReferenceSystem &srs, bool emitSign { mCRS = srs; - if ( isSpatial() && !mCRS.isValid() ) + if ( mShouldValidateCrs && isSpatial() && !mCRS.isValid() ) { mCRS.setValidationHint( tr( "Specify CRS for layer %1" ).arg( name() ) ); mCRS.validate(); diff --git a/src/core/qgsmaplayer.h b/src/core/qgsmaplayer.h index 70e368da208..b419a1ad4fc 100644 --- a/src/core/qgsmaplayer.h +++ b/src/core/qgsmaplayer.h @@ -1533,6 +1533,13 @@ class CORE_EXPORT QgsMapLayer : public QObject //! Read flags. It's up to the subclass to respect these when restoring state from XML QgsMapLayer::ReadFlags mReadFlags = nullptr; + /** + * TRUE if the layer's CRS should be validated and invalid CRSes are not permitted. + * + * \since QGIS 3.10 + */ + bool mShouldValidateCrs = true; + private: virtual QString baseURI( PropertyType type ) const; diff --git a/src/core/qgsvectorlayer.cpp b/src/core/qgsvectorlayer.cpp index 5b3dfde8920..2b8bf15cdea 100644 --- a/src/core/qgsvectorlayer.cpp +++ b/src/core/qgsvectorlayer.cpp @@ -150,6 +150,8 @@ QgsVectorLayer::QgsVectorLayer( const QString &vectorLayerPath, , mAuxiliaryLayerKey( QString() ) , mReadExtentFromXml( options.readExtentFromXml ) { + mShouldValidateCrs = !options.allowInvalidCrs; + if ( options.fallbackCrs.isValid() ) setCrs( options.fallbackCrs, false ); mWkbType = options.fallbackWkbType; diff --git a/src/core/qgsvectorlayer.h b/src/core/qgsvectorlayer.h index db4a547a4f2..d7309a872f8 100644 --- a/src/core/qgsvectorlayer.h +++ b/src/core/qgsvectorlayer.h @@ -465,6 +465,21 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte */ QgsCoordinateReferenceSystem fallbackCrs; + /** + * Controls whether the layer is allowed to have an invalid/unknown CRS. + * + * If TRUE, then no validation will be performed on the layer's CRS and the layer + * layer's crs() may be invalid() (i.e. the layer will have no georeferencing available + * and will be treated as having purely numerical coordinates). + * + * If FALSE (the default), the layer's CRS will be validated using QgsCoordinateReferenceSystem::validate(), + * which may cause a blocking, user-facing dialog asking users to manually select the correct CRS for the + * layer. + * + * \since QGIS 3.10 + */ + bool allowInvalidCrs = false; + }; /** diff --git a/src/core/raster/qgsrasterlayer.cpp b/src/core/raster/qgsrasterlayer.cpp index 21cda45bdaf..1b7947e6c91 100644 --- a/src/core/raster/qgsrasterlayer.cpp +++ b/src/core/raster/qgsrasterlayer.cpp @@ -117,6 +117,8 @@ QgsRasterLayer::QgsRasterLayer( const QString &uri, , QSTRING_NOT_SET( QStringLiteral( "Not Set" ) ) , TRSTRING_NOT_SET( tr( "Not Set" ) ) { + mShouldValidateCrs = !options.allowInvalidCrs; + QgsDebugMsgLevel( QStringLiteral( "Entered" ), 4 ); setProviderType( providerKey ); diff --git a/src/core/raster/qgsrasterlayer.h b/src/core/raster/qgsrasterlayer.h index a5491a6226e..ff1b8512009 100644 --- a/src/core/raster/qgsrasterlayer.h +++ b/src/core/raster/qgsrasterlayer.h @@ -188,6 +188,21 @@ class CORE_EXPORT QgsRasterLayer : public QgsMapLayer */ QgsCoordinateTransformContext transformContext = QgsCoordinateTransformContext(); + /** + * Controls whether the layer is allowed to have an invalid/unknown CRS. + * + * If TRUE, then no validation will be performed on the layer's CRS and the layer + * layer's crs() may be invalid() (i.e. the layer will have no georeferencing available + * and will be treated as having purely numerical coordinates). + * + * If FALSE (the default), the layer's CRS will be validated using QgsCoordinateReferenceSystem::validate(), + * which may cause a blocking, user-facing dialog asking users to manually select the correct CRS for the + * layer. + * + * \since QGIS 3.10 + */ + bool allowInvalidCrs = false; + }; /** diff --git a/src/gui/qgsbrowserdockwidget_p.cpp b/src/gui/qgsbrowserdockwidget_p.cpp index c6933c0d404..0072d55b251 100644 --- a/src/gui/qgsbrowserdockwidget_p.cpp +++ b/src/gui/qgsbrowserdockwidget_p.cpp @@ -161,26 +161,6 @@ QgsBrowserLayerProperties::QgsBrowserLayerProperties( QWidget *parent ) } ); } -class ProjectionSettingRestorer -{ - public: - - ProjectionSettingRestorer() - { - QgsSettings settings; - previousSetting = settings.value( QStringLiteral( "/Projections/defaultBehavior" ) ).toString(); - settings.setValue( QStringLiteral( "/Projections/defaultBehavior" ), QStringLiteral( "useProject" ) ); - } - - ~ProjectionSettingRestorer() - { - QgsSettings settings; - settings.setValue( QStringLiteral( "/Projections/defaultBehavior" ), previousSetting ); - } - - QString previousSetting; -}; - void QgsBrowserLayerProperties::setItem( QgsDataItem *item ) { QgsLayerItem *layerItem = qobject_cast( item ); @@ -191,13 +171,6 @@ void QgsBrowserLayerProperties::setItem( QgsDataItem *item ) QgsMapLayerType type = layerItem->mapLayerType(); QString layerMetadata = tr( "Error" ); - QgsCoordinateReferenceSystem layerCrs; - - QString defaultProjectionOption = QgsSettings().value( QStringLiteral( "Projections/defaultBehavior" ), "prompt" ).toString(); - // temporarily override /Projections/defaultBehavior to avoid dialog prompt - // TODO - remove when there is a cleaner way to block the unknown projection dialog! - ProjectionSettingRestorer restorer; - ( void )restorer; // no warnings mLayer.reset(); @@ -211,14 +184,17 @@ void QgsBrowserLayerProperties::setItem( QgsDataItem *item ) { QgsDebugMsg( QStringLiteral( "creating raster layer" ) ); // should copy code from addLayer() to split uri ? - mLayer = qgis::make_unique< QgsRasterLayer >( layerItem->uri(), layerItem->name(), layerItem->providerKey() ); + QgsRasterLayer::LayerOptions options; + options.allowInvalidCrs = true; + mLayer = qgis::make_unique< QgsRasterLayer >( layerItem->uri(), layerItem->name(), layerItem->providerKey(), options ); break; } case QgsMapLayerType::MeshLayer: { QgsDebugMsg( QStringLiteral( "creating mesh layer" ) ); - const QgsMeshLayer::LayerOptions options { QgsProject::instance()->transformContext() }; + QgsMeshLayer::LayerOptions options { QgsProject::instance()->transformContext() }; + options.allowInvalidCrs = true; mLayer = qgis::make_unique < QgsMeshLayer >( layerItem->uri(), layerItem->name(), layerItem->providerKey(), options ); break; } @@ -226,7 +202,8 @@ void QgsBrowserLayerProperties::setItem( QgsDataItem *item ) case QgsMapLayerType::VectorLayer: { QgsDebugMsg( QStringLiteral( "creating vector layer" ) ); - const QgsVectorLayer::LayerOptions options { QgsProject::instance()->transformContext() }; + QgsVectorLayer::LayerOptions options { QgsProject::instance()->transformContext() }; + options.allowInvalidCrs = true; mLayer = qgis::make_unique < QgsVectorLayer>( layerItem->uri(), layerItem->name(), layerItem->providerKey(), options ); break; } @@ -249,7 +226,6 @@ void QgsBrowserLayerProperties::setItem( QgsDataItem *item ) { bool ok = false; mLayer->loadDefaultMetadata( ok ); - layerCrs = mLayer->crs(); layerMetadata = mLayer->htmlMetadata(); mMapCanvas->setDestinationCrs( mLayer->crs() ); @@ -267,15 +243,6 @@ void QgsBrowserLayerProperties::setItem( QgsDataItem *item ) mMetadataTextBrowser->document()->setDefaultStyleSheet( myStyle ); mMetadataTextBrowser->setHtml( layerMetadata ); -// report if layer was set to to project crs without prompt (may give a false positive) - if ( defaultProjectionOption == QLatin1String( "prompt" ) ) - { - QgsCoordinateReferenceSystem defaultCrs = - QgsProject::instance()->crs(); - if ( layerCrs == defaultCrs ) - mNoticeLabel->setText( "NOTICE: Layer CRS set from project (" + defaultCrs.authid() + ')' ); - } - if ( mNoticeLabel->text().isEmpty() ) { mNoticeLabel->hide(); diff --git a/src/gui/qgsrasterformatsaveoptionswidget.cpp b/src/gui/qgsrasterformatsaveoptionswidget.cpp index f4d93cc33f9..50a76a7e941 100644 --- a/src/gui/qgsrasterformatsaveoptionswidget.cpp +++ b/src/gui/qgsrasterformatsaveoptionswidget.cpp @@ -302,22 +302,10 @@ QString QgsRasterFormatSaveOptionsWidget::validateOptions( bool gui, bool report bool tmpLayer = false; if ( !( mRasterLayer && rasterLayer->dataProvider() ) && ! mRasterFileName.isNull() ) { - // temporarily override /Projections/defaultBehavior to avoid dialog prompt - // this is taken from qgsbrowserdockwidget.cpp - // TODO - integrate this into qgis core - QgsSettings settings; - QString defaultProjectionOption = settings.value( QStringLiteral( "Projections/defaultBehavior" ), "prompt" ).toString(); - if ( settings.value( QStringLiteral( "Projections/defaultBehavior" ), "prompt" ).toString() == QLatin1String( "prompt" ) ) - { - settings.setValue( QStringLiteral( "Projections/defaultBehavior" ), "useProject" ); - } tmpLayer = true; - rasterLayer = new QgsRasterLayer( mRasterFileName, QFileInfo( mRasterFileName ).baseName(), QStringLiteral( "gdal" ) ); - // restore /Projections/defaultBehavior - if ( defaultProjectionOption == QLatin1String( "prompt" ) ) - { - settings.setValue( QStringLiteral( "Projections/defaultBehavior" ), defaultProjectionOption ); - } + QgsRasterLayer::LayerOptions options; + options.allowInvalidCrs = true; + rasterLayer = new QgsRasterLayer( mRasterFileName, QFileInfo( mRasterFileName ).baseName(), QStringLiteral( "gdal" ), options ); } if ( mProvider == QLatin1String( "gdal" ) && mPyramids )