Skip to content

Commit

Permalink
Show user-facing warnings when coordinate transform between two
Browse files Browse the repository at this point in the history
dynamic CRSes at different coordinate epochs is attempted

This is not currently supported by PROJ, so the results will be
misleading.
  • Loading branch information
nyalldawson committed May 13, 2021
1 parent a703e87 commit c0743d6
Show file tree
Hide file tree
Showing 7 changed files with 143 additions and 1 deletion.
42 changes: 42 additions & 0 deletions src/app/qgsappcoordinateoperationhandlers.cpp
Expand Up @@ -63,11 +63,18 @@ QgsAppMissingGridHandler::QgsAppMissingGridHandler( QObject *parent )
emit fallbackOperationOccurred( sourceCrs, destinationCrs, desired );
} );

QgsCoordinateTransform::setDynamicCrsToDynamicCrsWarningHandler( [ = ]( const QgsCoordinateReferenceSystem & sourceCrs,
const QgsCoordinateReferenceSystem & destinationCrs )
{
emit dynamicToDynamicWarning( sourceCrs, destinationCrs );
} );

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( this, &QgsAppMissingGridHandler::fallbackOperationOccurred, this, &QgsAppMissingGridHandler::onFallbackOperationOccurred, Qt::QueuedConnection );
connect( this, &QgsAppMissingGridHandler::dynamicToDynamicWarning, this, &QgsAppMissingGridHandler::onDynamicToDynamicWarning, Qt::QueuedConnection );

connect( QgsProject::instance(), &QgsProject::cleared, this, [ = ]
{
Expand Down Expand Up @@ -301,6 +308,30 @@ void QgsAppMissingGridHandler::onFallbackOperationOccurred( const QgsCoordinateR
bar->pushWidget( widget, Qgis::Warning, 0 );
}

void QgsAppMissingGridHandler::onDynamicToDynamicWarning( const QgsCoordinateReferenceSystem &sourceCrs, const QgsCoordinateReferenceSystem &destinationCrs )
{
if ( !shouldWarnAboutDynamicCrsForCurrentProject( sourceCrs, destinationCrs ) )
return;

const QString shortMessage = tr( "Cannot transform between dynamic CRS at difference coordinate epochs" );
const QString longMessage = tr( "<p>Transformation between %1 and %3 is not currently supported.</p><p><b>The results will be unpredictable and should not be used for high accuracy work.</b>" ).arg( sourceCrs.userFriendlyIdentifier(), destinationCrs.userFriendlyIdentifier() );

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( "Unsupported Transformation" ) );
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 Down Expand Up @@ -333,3 +364,14 @@ bool QgsAppMissingGridHandler::shouldWarnAboutBallparkPairForCurrentProject( con
mAlreadyWarnedBallparkPairsForProject.append( qMakePair( source, dest ) );
return true;
}

bool QgsAppMissingGridHandler::shouldWarnAboutDynamicCrsForCurrentProject( const QgsCoordinateReferenceSystem &source, const QgsCoordinateReferenceSystem &dest )
{
if ( mAlreadyWarnedDynamicCrsForProject.contains( qMakePair( source, dest ) ) || mAlreadyWarnedDynamicCrsForProject.contains( qMakePair( dest, source ) ) )
{
return false;
}

mAlreadyWarnedDynamicCrsForProject.append( qMakePair( source, dest ) );
return true;
}
8 changes: 8 additions & 0 deletions src/app/qgsappcoordinateoperationhandlers.h
Expand Up @@ -53,6 +53,9 @@ class QgsAppMissingGridHandler : public QObject
const QgsCoordinateReferenceSystem &destinationCrs,
const QString &desired );

void dynamicToDynamicWarning( const QgsCoordinateReferenceSystem &sourceCrs,
const QgsCoordinateReferenceSystem &destinationCrs );

private slots:

void onMissingRequiredGrid( const QgsCoordinateReferenceSystem &sourceCrs,
Expand All @@ -76,15 +79,20 @@ class QgsAppMissingGridHandler : public QObject
const QgsCoordinateReferenceSystem &destinationCrs,
const QString &desired );

void onDynamicToDynamicWarning( const QgsCoordinateReferenceSystem &sourceCrs,
const QgsCoordinateReferenceSystem &destinationCrs );

private:

bool shouldWarnAboutPair( const QgsCoordinateReferenceSystem &source, const QgsCoordinateReferenceSystem &dest );
bool shouldWarnAboutPairForCurrentProject( const QgsCoordinateReferenceSystem &source, const QgsCoordinateReferenceSystem &dest );
bool shouldWarnAboutBallparkPairForCurrentProject( const QgsCoordinateReferenceSystem &source, const QgsCoordinateReferenceSystem &dest );
bool shouldWarnAboutDynamicCrsForCurrentProject( const QgsCoordinateReferenceSystem &source, const QgsCoordinateReferenceSystem &dest );

QList< QPair< QgsCoordinateReferenceSystem, QgsCoordinateReferenceSystem > > mAlreadyWarnedPairs;
QList< QPair< QgsCoordinateReferenceSystem, QgsCoordinateReferenceSystem > > mAlreadyWarnedPairsForProject;
QList< QPair< QgsCoordinateReferenceSystem, QgsCoordinateReferenceSystem > > mAlreadyWarnedBallparkPairsForProject;
QList< QPair< QgsCoordinateReferenceSystem, QgsCoordinateReferenceSystem > > mAlreadyWarnedDynamicCrsForProject;
};

#endif // QGSAPPCOORDINATEOPERATIONHANDLERS_H
5 changes: 5 additions & 0 deletions src/core/proj/qgscoordinatetransform.cpp
Expand Up @@ -1052,3 +1052,8 @@ void QgsCoordinateTransform::setFallbackOperationOccurredHandler( const std::fun
{
sFallbackOperationOccurredHandler = handler;
}

void QgsCoordinateTransform::setDynamicCrsToDynamicCrsWarningHandler( const std::function<void ( const QgsCoordinateReferenceSystem &, const QgsCoordinateReferenceSystem & )> &handler )
{
QgsCoordinateTransformPrivate::setDynamicCrsToDynamicCrsWarningHandler( handler );
}
9 changes: 9 additions & 0 deletions src/core/proj/qgscoordinatetransform.h
Expand Up @@ -679,6 +679,15 @@ class CORE_EXPORT QgsCoordinateTransform
const QgsCoordinateReferenceSystem &destinationCrs,
const QString &desiredOperation )> &handler );

/**
* Sets a custom \a handler to use when the desired coordinate operation for use between \a sourceCrs and
* \a destinationCrs is a dynamic CRS to dynamic CRS transform, not currently supported by PROJ.
*
* \since QGIS 3.20
*/
static void setDynamicCrsToDynamicCrsWarningHandler( const std::function< void( const QgsCoordinateReferenceSystem &sourceCrs,
const QgsCoordinateReferenceSystem &destinationCrs )> &handler );

#endif

private:
Expand Down
13 changes: 12 additions & 1 deletion src/core/proj/qgscoordinatetransform_p.cpp
Expand Up @@ -48,6 +48,9 @@ std::function< void( const QgsCoordinateReferenceSystem &sourceCrs,
const QgsCoordinateReferenceSystem &destinationCrs,
const QgsDatumTransform::TransformDetails &desiredOperation )> QgsCoordinateTransformPrivate::sMissingGridUsedByContextHandler = nullptr;

std::function< void( const QgsCoordinateReferenceSystem &sourceCrs,
const QgsCoordinateReferenceSystem &destinationCrs )> QgsCoordinateTransformPrivate::sDynamicCrsToDynamicCrsWarningHandler = nullptr;

Q_NOWARN_DEPRECATED_PUSH // because of deprecated members
QgsCoordinateTransformPrivate::QgsCoordinateTransformPrivate()
{
Expand Down Expand Up @@ -174,7 +177,10 @@ bool QgsCoordinateTransformPrivate::initialize()
&& !std::isnan( mSourceCoordinateEpoch ) && mSourceCoordinateEpoch != mDestCoordinateEpoch )
{
// transforms from dynamic crs to dynamic crs with different coordinate epochs are not yet supported by PROJ

if ( sDynamicCrsToDynamicCrsWarningHandler )
{
sDynamicCrsToDynamicCrsWarningHandler( mSourceCRS, mDestCRS );
}
}

// init the projections (destination and source)
Expand Down Expand Up @@ -560,6 +566,11 @@ void QgsCoordinateTransformPrivate::setCustomMissingGridUsedByContextHandler( co
sMissingGridUsedByContextHandler = handler;
}

void QgsCoordinateTransformPrivate::setDynamicCrsToDynamicCrsWarningHandler( const std::function<void ( const QgsCoordinateReferenceSystem &, const QgsCoordinateReferenceSystem & )> &handler )
{
sDynamicCrsToDynamicCrsWarningHandler = handler;
}

void QgsCoordinateTransformPrivate::freeProj()
{
QgsReadWriteLocker locker( mProjLock, QgsReadWriteLocker::Write );
Expand Down
12 changes: 12 additions & 0 deletions src/core/proj/qgscoordinatetransform_p.h
Expand Up @@ -165,6 +165,15 @@ class QgsCoordinateTransformPrivate : public QSharedData
const QgsCoordinateReferenceSystem &destinationCrs,
const QgsDatumTransform::TransformDetails &desiredOperation )> &handler );

/**
* Sets a custom \a handler to use when the desired coordinate operation for use between \a sourceCrs and
* \a destinationCrs is a dynamic CRS to dynamic CRS transform, not currently supported by PROJ.
*
* \since QGIS 3.20
*/
static void setDynamicCrsToDynamicCrsWarningHandler( const std::function< void( const QgsCoordinateReferenceSystem &sourceCrs,
const QgsCoordinateReferenceSystem &destinationCrs )> &handler );

private:

void freeProj();
Expand All @@ -186,6 +195,9 @@ class QgsCoordinateTransformPrivate : public QSharedData
const QgsCoordinateReferenceSystem &destinationCrs,
const QgsDatumTransform::TransformDetails &desiredOperation )> sMissingGridUsedByContextHandler;

static std::function< void( const QgsCoordinateReferenceSystem &sourceCrs,
const QgsCoordinateReferenceSystem &destinationCrs )> sDynamicCrsToDynamicCrsWarningHandler;

QgsCoordinateTransformPrivate &operator= ( const QgsCoordinateTransformPrivate & ) = delete;
};

Expand Down
55 changes: 55 additions & 0 deletions tests/src/core/testqgscoordinatetransform.cpp
Expand Up @@ -44,6 +44,7 @@ class TestQgsCoordinateTransform: public QObject
#if PROJ_VERSION_MAJOR>7 || (PROJ_VERSION_MAJOR == 7 && PROJ_VERSION_MINOR >= 2)
void transformEpoch_data();
void transformEpoch();
void dynamicToDynamicErrorHandler();
#endif
void transformLKS();
void transformContextNormalize();
Expand Down Expand Up @@ -463,6 +464,60 @@ void TestQgsCoordinateTransform::transformEpoch()
QGSCOMPARENEAR( x, outX, precision );
QGSCOMPARENEAR( y, outY, precision );
}

void TestQgsCoordinateTransform::dynamicToDynamicErrorHandler()
{
// test that warnings are raised when attempting a dynamic crs to dynamic crs transformation (not supported by PROJ)
int counter = 0;
QgsCoordinateTransform::setDynamicCrsToDynamicCrsWarningHandler( [&counter]( const QgsCoordinateReferenceSystem &, const QgsCoordinateReferenceSystem & )
{
counter++;
} );

// no warnings -- no coordinate epoch set (although we should consider a different warning in this situation!!)
QgsCoordinateReferenceSystem src( QStringLiteral( "EPSG:9000" ) );
QgsCoordinateReferenceSystem dest( QStringLiteral( "EPSG:9000" ) );
QgsCoordinateTransform t( src, dest, QgsCoordinateTransformContext() );
QCOMPARE( counter, 0 );

// no warnings, static to static
src = QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:7844" ) );
dest = QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3111" ) );
t = QgsCoordinateTransform( src, dest, QgsCoordinateTransformContext() );
QCOMPARE( counter, 0 );

// no warnings, static to dynamic
src = QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:7844" ) );
dest = QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:9000" ) );
dest.setCoordinateEpoch( 2030 );
t = QgsCoordinateTransform( src, dest, QgsCoordinateTransformContext() );
QCOMPARE( counter, 0 );

// no warnings, dynamic to static
src = QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:9000" ) );
src.setCoordinateEpoch( 2030 );
dest = QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:7844" ) );
t = QgsCoordinateTransform( src, dest, QgsCoordinateTransformContext() );
QCOMPARE( counter, 0 );

// no warnings, same dynamic CRS to same dynamic CRS with same epoch
src = QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:9000" ) );
src.setCoordinateEpoch( 2030 );
dest = QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:9000" ) );
dest.setCoordinateEpoch( 2030 );
t = QgsCoordinateTransform( src, dest, QgsCoordinateTransformContext() );
QCOMPARE( counter, 0 );

// yes warnings, dynamic CRS to dynamic CRS with different epoch
src = QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:9000" ) );
src.setCoordinateEpoch( 2030 );
dest = QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:9000" ) );
dest.setCoordinateEpoch( 2025 );
t = QgsCoordinateTransform( src, dest, QgsCoordinateTransformContext() );
QCOMPARE( counter, 1 );

QgsCoordinateTransform::setDynamicCrsToDynamicCrsWarningHandler( nullptr );
}
#endif

void TestQgsCoordinateTransform::transformBoundingBox()
Expand Down

0 comments on commit c0743d6

Please sign in to comment.