[api] Add API to indicate that individual layers may be loaded without any

CRS validation, regardless of the user's settings

This avoids hacks put in place in other parts of QGIS code or in plugins
to temporarily deactivate the CRS validation prompt, providing a supported,
stable method to indicate that when loading a particular layer no CRS
validation is required.
This commit is contained in:
Nyall Dawson 2019-10-07 12:51:27 +10:00
parent 71ddea9bd2
commit cb06519d16
17 changed files with 90 additions and 95 deletions

View File

@ -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" ),

View File

@ -1679,6 +1679,7 @@ this method is now deprecated and always return false, because circular dependen
};
QFlags<QgsMapLayer::LayerFlag> operator|(QgsMapLayer::LayerFlag f1, QFlags<QgsMapLayer::LayerFlag> f2);

View File

@ -352,6 +352,8 @@ Constructor for LayerOptions.
QgsCoordinateReferenceSystem fallbackCrs;
bool allowInvalidCrs;
};
explicit QgsVectorLayer( const QString &path = QString(), const QString &baseName = QString(),

View File

@ -114,6 +114,8 @@ Constructor for LayerOptions.
QgsCoordinateTransformContext transformContext;
bool allowInvalidCrs;
};
explicit QgsRasterLayer( const QString &uri,

View File

@ -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

View File

@ -42,6 +42,8 @@ QgsMeshLayer::QgsMeshLayer( const QString &meshLayerPath,
const QgsMeshLayer::LayerOptions &options )
: QgsMapLayer( QgsMapLayerType::MeshLayer, baseName, meshLayerPath )
{
mShouldValidateCrs = !options.allowInvalidCrs;
setProviderType( providerKey );
// if were given a provider type, try to create and bind one to this layer
if ( !meshLayerPath.isEmpty() && !providerKey.isEmpty() )

View File

@ -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;
};
/**

View File

@ -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<QgsVectorLayer>( 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() )
{

View File

@ -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 );
}

View File

@ -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();

View File

@ -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;

View File

@ -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;

View File

@ -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;
};
/**

View File

@ -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 );

View File

@ -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;
};
/**

View File

@ -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<QgsLayerItem *>( 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();

View File

@ -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 )