Skip to content
Permalink
Browse files
Show user-facing warnings when coordinate transform between two
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 c0743d6bef824a52cdf4d77800106b6609858535
@@ -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, [ = ]
{
@@ -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 ) ) )
@@ -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;
}
@@ -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,
@@ -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
@@ -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 );
}
@@ -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:
@@ -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()
{
@@ -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)
@@ -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 );
@@ -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();
@@ -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;
};

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

0 comments on commit c0743d6

Please sign in to comment.