Skip to content

Commit

Permalink
Add friendly, descriptive errors when opening a project which has
Browse files Browse the repository at this point in the history
preset non-default coordinate operations set between a crs pair,
which use grids that are not available on the local system
  • Loading branch information
nyalldawson committed Jun 3, 2019
1 parent fe4ea33 commit 789dbb5
Show file tree
Hide file tree
Showing 10 changed files with 161 additions and 3 deletions.
Expand Up @@ -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``.
Expand Down
77 changes: 77 additions & 0 deletions src/app/qgsappcoordinateoperationhandlers.cpp
Expand Up @@ -18,6 +18,7 @@
#include "qgsmessagebar.h"
#include "qgsmessagebaritem.h"
#include "qgsmessageoutput.h"
#include "qgsproject.h"

//
// QgsAppMissingRequiredGridHandler
Expand Down Expand Up @@ -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(); } );

}

Expand Down Expand Up @@ -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 ) ) )
Expand All @@ -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;
}
10 changes: 10 additions & 0 deletions src/app/qgsappcoordinateoperationhandlers.h
Expand Up @@ -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,
Expand All @@ -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;

};

Expand Down
5 changes: 5 additions & 0 deletions src/core/qgscoordinatetransform.cpp
Expand Up @@ -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 );
}
18 changes: 18 additions & 0 deletions src/core/qgscoordinatetransform.h
Expand Up @@ -447,6 +447,7 @@ class CORE_EXPORT QgsCoordinateTransform
*
* \see setCustomMissingPreferredGridHandler()
* \see setCustomCoordinateOperationCreationErrorHandler()
* \see setCustomMissingGridUsedByContextHandler()
*
* \note Not available in Python bindings
* \since QGIS 3.8
Expand All @@ -466,6 +467,7 @@ class CORE_EXPORT QgsCoordinateTransform
*
* \see setCustomMissingRequiredGridHandler()
* \see setCustomCoordinateOperationCreationErrorHandler()
* \see setCustomMissingGridUsedByContextHandler()
*
* \note Not available in Python bindings
* \since QGIS 3.8
Expand All @@ -482,6 +484,7 @@ class CORE_EXPORT QgsCoordinateTransform
*
* \see setCustomMissingRequiredGridHandler()
* \see setCustomMissingPreferredGridHandler()
* \see setCustomMissingGridUsedByContextHandler()
*
* \note Not available in Python bindings
* \since QGIS 3.8
Expand All @@ -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:
Expand Down
31 changes: 31 additions & 0 deletions src/core/qgscoordinatetransform_p.cpp
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
{
Expand Down
15 changes: 15 additions & 0 deletions src/core/qgscoordinatetransform_p.h
Expand Up @@ -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
Expand All @@ -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
Expand Down
3 changes: 1 addition & 2 deletions src/core/qgscoordinatetransformcontext.cpp
Expand Up @@ -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;
}

Expand Down
2 changes: 2 additions & 0 deletions src/core/qgscoordinatetransformcontext.h
Expand Up @@ -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.
*
Expand Down
2 changes: 1 addition & 1 deletion src/core/qgsproject.cpp
Expand Up @@ -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 );
}
Expand Down

0 comments on commit 789dbb5

Please sign in to comment.