diff --git a/python/core/auto_generated/qgscoordinatetransformcontext.sip.in b/python/core/auto_generated/qgscoordinatetransformcontext.sip.in
index 0984e16476f..918602ec898 100644
--- a/python/core/auto_generated/qgscoordinatetransformcontext.sip.in
+++ b/python/core/auto_generated/qgscoordinatetransformcontext.sip.in
@@ -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``.
diff --git a/src/app/qgsappcoordinateoperationhandlers.cpp b/src/app/qgsappcoordinateoperationhandlers.cpp
index 773024e3d1f..8efb24621f9 100644
--- a/src/app/qgsappcoordinateoperationhandlers.cpp
+++ b/src/app/qgsappcoordinateoperationhandlers.cpp
@@ -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 %1 package, available for download from %2." ).arg( grid.packageName, grid.url );
+ }
+ else
+ {
+ m += ' ' + tr( "This grid is available for download from %1." ).arg( grid.url );
+ }
+ }
+ gridMessage += QStringLiteral( "
%1" ).arg( m );
+ }
+ }
+ if ( !gridMessage.isEmpty() )
+ {
+ gridMessage = "";
+ }
+
+ const QString longMessage = tr( "This project specifies a preset transform between %1 and %2, which is not available for use on the system.
" ).arg( displayIdentifierForCrs( sourceCrs ),
+ displayIdentifierForCrs( destinationCrs ) )
+ + gridMessage
+ + tr( "The operation specified for use in the project is:
%1
" ).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;
+}
diff --git a/src/app/qgsappcoordinateoperationhandlers.h b/src/app/qgsappcoordinateoperationhandlers.h
index e33184615e2..083e7e35344 100644
--- a/src/app/qgsappcoordinateoperationhandlers.h
+++ b/src/app/qgsappcoordinateoperationhandlers.h
@@ -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;
};
diff --git a/src/core/qgscoordinatetransform.cpp b/src/core/qgscoordinatetransform.cpp
index 3886fe47376..65e701caa0a 100644
--- a/src/core/qgscoordinatetransform.cpp
+++ b/src/core/qgscoordinatetransform.cpp
@@ -969,3 +969,8 @@ void QgsCoordinateTransform::setCustomCoordinateOperationCreationErrorHandler( c
{
QgsCoordinateTransformPrivate::setCustomCoordinateOperationCreationErrorHandler( handler );
}
+
+void QgsCoordinateTransform::setCustomMissingGridUsedByContextHandler( const std::function &handler )
+{
+ QgsCoordinateTransformPrivate::setCustomMissingGridUsedByContextHandler( handler );
+}
diff --git a/src/core/qgscoordinatetransform.h b/src/core/qgscoordinatetransform.h
index 5768a7f39ae..2009aeec4a4 100644
--- a/src/core/qgscoordinatetransform.h
+++ b/src/core/qgscoordinatetransform.h
@@ -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:
diff --git a/src/core/qgscoordinatetransform_p.cpp b/src/core/qgscoordinatetransform_p.cpp
index b30eda061b7..23e54edf6e4 100644
--- a/src/core/qgscoordinatetransform_p.cpp
+++ b/src/core/qgscoordinatetransform_p.cpp
@@ -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 &handler )
+{
+ sMissingGridUsedByContextHandler = handler;
+}
+
#if PROJ_VERSION_MAJOR<6
QString QgsCoordinateTransformPrivate::stripDatumTransform( const QString &proj4 ) const
{
diff --git a/src/core/qgscoordinatetransform_p.h b/src/core/qgscoordinatetransform_p.h
index 38a4c4e9f9a..4f79ab53a76 100644
--- a/src/core/qgscoordinatetransform_p.h
+++ b/src/core/qgscoordinatetransform_p.h
@@ -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
diff --git a/src/core/qgscoordinatetransformcontext.cpp b/src/core/qgscoordinatetransformcontext.cpp
index ba35e2a8f85..e6796b0b495 100644
--- a/src/core/qgscoordinatetransformcontext.cpp
+++ b/src/core/qgscoordinatetransformcontext.cpp
@@ -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;
}
diff --git a/src/core/qgscoordinatetransformcontext.h b/src/core/qgscoordinatetransformcontext.h
index 5644e1a47dc..b7d644298e7 100644
--- a/src/core/qgscoordinatetransformcontext.h
+++ b/src/core/qgscoordinatetransformcontext.h
@@ -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.
*
diff --git a/src/core/qgsproject.cpp b/src/core/qgsproject.cpp
index ea5382c1f64..94a40484fd8 100644
--- a/src/core/qgsproject.cpp
+++ b/src/core/qgsproject.cpp
@@ -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 );
}