mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-13 00:03:09 -04:00
Add friendly, descriptive errors when opening a project which has
preset non-default coordinate operations set between a crs pair, which use grids that are not available on the local system
This commit is contained in:
parent
fe4ea3318a
commit
789dbb570b
@ -212,6 +212,7 @@ be used.
|
||||
.. versionadded:: 3.8
|
||||
%End
|
||||
|
||||
|
||||
bool readXml( const QDomElement &element, const QgsReadWriteContext &context, QStringList &missingTransforms /Out/ );
|
||||
%Docstring
|
||||
Reads the context's state from a DOM ``element``.
|
||||
|
@ -18,6 +18,7 @@
|
||||
#include "qgsmessagebar.h"
|
||||
#include "qgsmessagebaritem.h"
|
||||
#include "qgsmessageoutput.h"
|
||||
#include "qgsproject.h"
|
||||
|
||||
//
|
||||
// QgsAppMissingRequiredGridHandler
|
||||
@ -47,9 +48,19 @@ QgsAppMissingGridHandler::QgsAppMissingGridHandler( QObject *parent )
|
||||
emit coordinateOperationCreationError( sourceCrs, destinationCrs, error );
|
||||
} );
|
||||
|
||||
QgsCoordinateTransform::setCustomMissingGridUsedByContextHandler( [ = ]( const QgsCoordinateReferenceSystem & sourceCrs,
|
||||
const QgsCoordinateReferenceSystem & destinationCrs,
|
||||
const QgsDatumTransform::TransformDetails & desired )
|
||||
{
|
||||
emit missingGridUsedByContextHandler( sourceCrs, destinationCrs, desired );
|
||||
} );
|
||||
|
||||
connect( this, &QgsAppMissingGridHandler::missingRequiredGrid, this, &QgsAppMissingGridHandler::onMissingRequiredGrid, Qt::QueuedConnection );
|
||||
connect( this, &QgsAppMissingGridHandler::missingPreferredGrid, this, &QgsAppMissingGridHandler::onMissingPreferredGrid, Qt::QueuedConnection );
|
||||
connect( this, &QgsAppMissingGridHandler::coordinateOperationCreationError, this, &QgsAppMissingGridHandler::onCoordinateOperationCreationError, Qt::QueuedConnection );
|
||||
connect( this, &QgsAppMissingGridHandler::missingGridUsedByContextHandler, this, &QgsAppMissingGridHandler::onMissingGridUsedByContextHandler, Qt::QueuedConnection );
|
||||
|
||||
connect( QgsProject::instance(), &QgsProject::cleared, this, [ = ] { mAlreadyWarnedPairsForProject.clear(); } );
|
||||
|
||||
}
|
||||
|
||||
@ -197,6 +208,61 @@ void QgsAppMissingGridHandler::onCoordinateOperationCreationError( const QgsCoor
|
||||
bar->pushWidget( widget, Qgis::Critical, 0 );
|
||||
}
|
||||
|
||||
void QgsAppMissingGridHandler::onMissingGridUsedByContextHandler( const QgsCoordinateReferenceSystem &sourceCrs, const QgsCoordinateReferenceSystem &destinationCrs, const QgsDatumTransform::TransformDetails &desired )
|
||||
{
|
||||
if ( !shouldWarnAboutPairForCurrentProject( sourceCrs, destinationCrs ) )
|
||||
return;
|
||||
|
||||
const QString shortMessage = tr( "Cannot use project transform between %1 and %2" ).arg( displayIdentifierForCrs( sourceCrs, true ),
|
||||
displayIdentifierForCrs( destinationCrs, true ) );
|
||||
|
||||
QString gridMessage;
|
||||
for ( const QgsDatumTransform::GridDetails &grid : desired.grids )
|
||||
{
|
||||
if ( !grid.isAvailable )
|
||||
{
|
||||
QString m = tr( "This transformation requires the grid file “%1”, which is not available for use on the system." ).arg( grid.shortName );
|
||||
if ( !grid.url.isEmpty() )
|
||||
{
|
||||
if ( !grid.packageName.isEmpty() )
|
||||
{
|
||||
m += ' ' + tr( "This grid is part of the <i>%1</i> package, available for download from <a href=\"%2\">%2</a>." ).arg( grid.packageName, grid.url );
|
||||
}
|
||||
else
|
||||
{
|
||||
m += ' ' + tr( "This grid is available for download from <a href=\"%1\">%1</a>." ).arg( grid.url );
|
||||
}
|
||||
}
|
||||
gridMessage += QStringLiteral( "<li>%1</li>" ).arg( m );
|
||||
}
|
||||
}
|
||||
if ( !gridMessage.isEmpty() )
|
||||
{
|
||||
gridMessage = "<ul>" + gridMessage + "</ul>";
|
||||
}
|
||||
|
||||
const QString longMessage = tr( "<p>This project specifies a preset transform between <i>%1</i> and <i>%2</i>, which is not available for use on the system.</p>" ).arg( displayIdentifierForCrs( sourceCrs ),
|
||||
displayIdentifierForCrs( destinationCrs ) )
|
||||
+ gridMessage
|
||||
+ tr( "<p>The operation specified for use in the project is:</p><p><code>%1</code></p>" ).arg( desired.proj ) ;
|
||||
|
||||
|
||||
QgsMessageBar *bar = QgisApp::instance()->messageBar();
|
||||
QgsMessageBarItem *widget = bar->createMessage( QString(), shortMessage );
|
||||
QPushButton *detailsButton = new QPushButton( tr( "Details" ) );
|
||||
connect( detailsButton, &QPushButton::clicked, this, [longMessage]
|
||||
{
|
||||
// dlg has deleted on close
|
||||
QgsMessageOutput * dlg( QgsMessageOutput::createMessageOutput() );
|
||||
dlg->setTitle( tr( "Project Transformation Not Available" ) );
|
||||
dlg->setMessage( longMessage, QgsMessageOutput::MessageHtml );
|
||||
dlg->showMessage();
|
||||
} );
|
||||
|
||||
widget->layout()->addWidget( detailsButton );
|
||||
bar->pushWidget( widget, Qgis::Critical, 0 );
|
||||
}
|
||||
|
||||
bool QgsAppMissingGridHandler::shouldWarnAboutPair( const QgsCoordinateReferenceSystem &source, const QgsCoordinateReferenceSystem &dest )
|
||||
{
|
||||
if ( mAlreadyWarnedPairs.contains( qMakePair( source, dest ) ) || mAlreadyWarnedPairs.contains( qMakePair( dest, source ) ) )
|
||||
@ -207,3 +273,14 @@ bool QgsAppMissingGridHandler::shouldWarnAboutPair( const QgsCoordinateReference
|
||||
mAlreadyWarnedPairs.append( qMakePair( source, dest ) );
|
||||
return true;
|
||||
}
|
||||
|
||||
bool QgsAppMissingGridHandler::shouldWarnAboutPairForCurrentProject( const QgsCoordinateReferenceSystem &source, const QgsCoordinateReferenceSystem &dest )
|
||||
{
|
||||
if ( mAlreadyWarnedPairsForProject.contains( qMakePair( source, dest ) ) || mAlreadyWarnedPairsForProject.contains( qMakePair( dest, source ) ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
mAlreadyWarnedPairsForProject.append( qMakePair( source, dest ) );
|
||||
return true;
|
||||
}
|
||||
|
@ -45,6 +45,10 @@ class QgsAppMissingGridHandler : public QObject
|
||||
const QgsCoordinateReferenceSystem &destinationCrs,
|
||||
const QString &error );
|
||||
|
||||
void missingGridUsedByContextHandler( const QgsCoordinateReferenceSystem &sourceCrs,
|
||||
const QgsCoordinateReferenceSystem &destinationCrs,
|
||||
const QgsDatumTransform::TransformDetails &desired );
|
||||
|
||||
private slots:
|
||||
|
||||
void onMissingRequiredGrid( const QgsCoordinateReferenceSystem &sourceCrs,
|
||||
@ -59,11 +63,17 @@ class QgsAppMissingGridHandler : public QObject
|
||||
void onCoordinateOperationCreationError( const QgsCoordinateReferenceSystem &sourceCrs,
|
||||
const QgsCoordinateReferenceSystem &destinationCrs,
|
||||
const QString &error );
|
||||
|
||||
void onMissingGridUsedByContextHandler( const QgsCoordinateReferenceSystem &sourceCrs,
|
||||
const QgsCoordinateReferenceSystem &destinationCrs,
|
||||
const QgsDatumTransform::TransformDetails &desired );
|
||||
private:
|
||||
|
||||
bool shouldWarnAboutPair( const QgsCoordinateReferenceSystem &source, const QgsCoordinateReferenceSystem &dest );
|
||||
bool shouldWarnAboutPairForCurrentProject( const QgsCoordinateReferenceSystem &source, const QgsCoordinateReferenceSystem &dest );
|
||||
|
||||
QList< QPair< QgsCoordinateReferenceSystem, QgsCoordinateReferenceSystem > > mAlreadyWarnedPairs;
|
||||
QList< QPair< QgsCoordinateReferenceSystem, QgsCoordinateReferenceSystem > > mAlreadyWarnedPairsForProject;
|
||||
|
||||
};
|
||||
|
||||
|
@ -969,3 +969,8 @@ void QgsCoordinateTransform::setCustomCoordinateOperationCreationErrorHandler( c
|
||||
{
|
||||
QgsCoordinateTransformPrivate::setCustomCoordinateOperationCreationErrorHandler( handler );
|
||||
}
|
||||
|
||||
void QgsCoordinateTransform::setCustomMissingGridUsedByContextHandler( const std::function<void ( const QgsCoordinateReferenceSystem &, const QgsCoordinateReferenceSystem &, const QgsDatumTransform::TransformDetails & )> &handler )
|
||||
{
|
||||
QgsCoordinateTransformPrivate::setCustomMissingGridUsedByContextHandler( handler );
|
||||
}
|
||||
|
@ -447,6 +447,7 @@ class CORE_EXPORT QgsCoordinateTransform
|
||||
*
|
||||
* \see setCustomMissingPreferredGridHandler()
|
||||
* \see setCustomCoordinateOperationCreationErrorHandler()
|
||||
* \see setCustomMissingGridUsedByContextHandler()
|
||||
*
|
||||
* \note Not available in Python bindings
|
||||
* \since QGIS 3.8
|
||||
@ -466,6 +467,7 @@ class CORE_EXPORT QgsCoordinateTransform
|
||||
*
|
||||
* \see setCustomMissingRequiredGridHandler()
|
||||
* \see setCustomCoordinateOperationCreationErrorHandler()
|
||||
* \see setCustomMissingGridUsedByContextHandler()
|
||||
*
|
||||
* \note Not available in Python bindings
|
||||
* \since QGIS 3.8
|
||||
@ -482,6 +484,7 @@ class CORE_EXPORT QgsCoordinateTransform
|
||||
*
|
||||
* \see setCustomMissingRequiredGridHandler()
|
||||
* \see setCustomMissingPreferredGridHandler()
|
||||
* \see setCustomMissingGridUsedByContextHandler()
|
||||
*
|
||||
* \note Not available in Python bindings
|
||||
* \since QGIS 3.8
|
||||
@ -490,6 +493,21 @@ class CORE_EXPORT QgsCoordinateTransform
|
||||
const QgsCoordinateReferenceSystem &destinationCrs,
|
||||
const QString &error )> &handler );
|
||||
|
||||
/**
|
||||
* Sets a custom handler to use when a coordinate operation was specified for use between \a sourceCrs and
|
||||
* \a destinationCrs by the transform context, yet the coordinate operation could not be created. The \a desiredOperation argument
|
||||
* specifies the desired transform details as specified by the context.
|
||||
*
|
||||
* \see setCustomMissingRequiredGridHandler()
|
||||
* \see setCustomMissingPreferredGridHandler()
|
||||
* \see setCustomCoordinateOperationCreationErrorHandler()
|
||||
*
|
||||
* \note Not available in Python bindings
|
||||
* \since QGIS 3.8
|
||||
*/
|
||||
static void setCustomMissingGridUsedByContextHandler( const std::function< void( const QgsCoordinateReferenceSystem &sourceCrs,
|
||||
const QgsCoordinateReferenceSystem &destinationCrs,
|
||||
const QgsDatumTransform::TransformDetails &desiredOperation )> &handler );
|
||||
#endif
|
||||
|
||||
private:
|
||||
|
@ -47,6 +47,10 @@ std::function< void( const QgsCoordinateReferenceSystem &sourceCrs,
|
||||
const QgsCoordinateReferenceSystem &destinationCrs,
|
||||
const QString &error )> QgsCoordinateTransformPrivate::sCoordinateOperationCreationErrorHandler = nullptr;
|
||||
|
||||
std::function< void( const QgsCoordinateReferenceSystem &sourceCrs,
|
||||
const QgsCoordinateReferenceSystem &destinationCrs,
|
||||
const QgsDatumTransform::TransformDetails &desiredOperation )> QgsCoordinateTransformPrivate::sMissingGridUsedByContextHandler = nullptr;
|
||||
|
||||
#if PROJ_VERSION_MAJOR<6
|
||||
#ifdef USE_THREAD_LOCAL
|
||||
thread_local QgsProjContextStore QgsCoordinateTransformPrivate::mProjContext;
|
||||
@ -298,7 +302,29 @@ ProjData QgsCoordinateTransformPrivate::threadLocalProjData()
|
||||
#if PROJ_VERSION_MAJOR>=6
|
||||
QgsProjUtils::proj_pj_unique_ptr transform;
|
||||
if ( !mProjCoordinateOperation.isEmpty() )
|
||||
{
|
||||
transform.reset( proj_create( context, mProjCoordinateOperation.toUtf8().constData() ) );
|
||||
if ( !transform || !proj_coordoperation_is_instantiable( context, transform.get() ) )
|
||||
{
|
||||
if ( sMissingGridUsedByContextHandler )
|
||||
{
|
||||
QgsDatumTransform::TransformDetails desired;
|
||||
desired.proj = mProjCoordinateOperation;
|
||||
desired.accuracy = -1; //unknown, can't retrieve from proj as we can't instantiate the op
|
||||
desired.grids = QgsProjUtils::gridsUsed( mProjCoordinateOperation );
|
||||
sMissingGridUsedByContextHandler( mSourceCRS, mDestCRS, desired );
|
||||
}
|
||||
else
|
||||
{
|
||||
const QString err = QObject::tr( "Could not use operation specified in project between %1 and %2. (Wanted to use: %3)." ).arg( mSourceCRS.authid() )
|
||||
.arg( mDestCRS.authid() )
|
||||
.arg( mProjCoordinateOperation );
|
||||
QgsMessageLog::logMessage( err, QString(), Qgis::Critical );
|
||||
}
|
||||
|
||||
transform.reset();
|
||||
}
|
||||
}
|
||||
|
||||
QString nonAvailableError;
|
||||
if ( !transform ) // fallback on default proj pathway
|
||||
@ -508,6 +534,11 @@ void QgsCoordinateTransformPrivate::setCustomCoordinateOperationCreationErrorHan
|
||||
sCoordinateOperationCreationErrorHandler = handler;
|
||||
}
|
||||
|
||||
void QgsCoordinateTransformPrivate::setCustomMissingGridUsedByContextHandler( const std::function<void ( const QgsCoordinateReferenceSystem &, const QgsCoordinateReferenceSystem &, const QgsDatumTransform::TransformDetails & )> &handler )
|
||||
{
|
||||
sMissingGridUsedByContextHandler = handler;
|
||||
}
|
||||
|
||||
#if PROJ_VERSION_MAJOR<6
|
||||
QString QgsCoordinateTransformPrivate::stripDatumTransform( const QString &proj4 ) const
|
||||
{
|
||||
|
@ -182,6 +182,17 @@ class QgsCoordinateTransformPrivate : public QSharedData
|
||||
const QgsCoordinateReferenceSystem &destinationCrs,
|
||||
const QString &error )> &handler );
|
||||
|
||||
/**
|
||||
* Sets a custom handler to use when a coordinate operation was specified for use between \a sourceCrs and
|
||||
* \a destinationCrs by the transform context, yet the coordinate operation could not be created. The \a desiredOperation argument
|
||||
* specifies the desired transform details as specified by the context.
|
||||
*
|
||||
* \since QGIS 3.8
|
||||
*/
|
||||
static void setCustomMissingGridUsedByContextHandler( const std::function< void( const QgsCoordinateReferenceSystem &sourceCrs,
|
||||
const QgsCoordinateReferenceSystem &destinationCrs,
|
||||
const QgsDatumTransform::TransformDetails &desiredOperation )> &handler );
|
||||
|
||||
private:
|
||||
|
||||
#if PROJ_VERSION_MAJOR<6
|
||||
@ -206,6 +217,10 @@ class QgsCoordinateTransformPrivate : public QSharedData
|
||||
static std::function< void( const QgsCoordinateReferenceSystem &sourceCrs,
|
||||
const QgsCoordinateReferenceSystem &destinationCrs,
|
||||
const QString &error )> sCoordinateOperationCreationErrorHandler;
|
||||
|
||||
static std::function< void( const QgsCoordinateReferenceSystem &sourceCrs,
|
||||
const QgsCoordinateReferenceSystem &destinationCrs,
|
||||
const QgsDatumTransform::TransformDetails &desiredOperation )> sMissingGridUsedByContextHandler;
|
||||
};
|
||||
|
||||
/// @endcond
|
||||
|
@ -223,8 +223,7 @@ bool QgsCoordinateTransformContext::readXml( const QDomElement &element, const Q
|
||||
if ( !QgsProjUtils::coordinateOperationIsAvailable( coordinateOp ) )
|
||||
{
|
||||
// not possible in current Proj 6 api!
|
||||
// missingTransforms.append( QgsProjUtils::nonAvailableGrids( coordinateOp ) );
|
||||
missingTransforms.append( coordinateOp ); // yuck, we don't want to expose this string to users!
|
||||
// QgsCoordinateTransform will alert users to this, we don't need to use missingTransforms here
|
||||
result = false;
|
||||
}
|
||||
|
||||
|
@ -205,6 +205,8 @@ class CORE_EXPORT QgsCoordinateTransformContext
|
||||
*/
|
||||
QString calculateCoordinateOperation( const QgsCoordinateReferenceSystem &source, const QgsCoordinateReferenceSystem &destination ) const;
|
||||
|
||||
// TODO QGIS 4.0 - remove missingTransforms, not used for Proj >= 6.0 builds
|
||||
|
||||
/**
|
||||
* Reads the context's state from a DOM \a element.
|
||||
*
|
||||
|
@ -1232,7 +1232,7 @@ bool QgsProject::readProjectFile( const QString &filename )
|
||||
mCrs = projectCrs;
|
||||
|
||||
QStringList datumErrors;
|
||||
if ( !mTransformContext.readXml( doc->documentElement(), context, datumErrors ) )
|
||||
if ( !mTransformContext.readXml( doc->documentElement(), context, datumErrors ) && !datumErrors.empty() )
|
||||
{
|
||||
emit missingDatumTransforms( datumErrors );
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user