Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Add a method to determine if the coordinate operation string returned
by QgsCoordinateTransformContext.calculateCoordinateOperation corresponds
to the reverse of what's actually required.

Gross API, but it's the best we can do until proj has a method to
invert a coordinate operation so that we can return the proper
inverse operation proj string from calculateCoordinateOperation
(without resorting to fragile proj string parsing/mangling)
  • Loading branch information
nyalldawson committed Dec 13, 2019
1 parent ef19797 commit a1ac778
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 0 deletions.
15 changes: 15 additions & 0 deletions python/core/auto_generated/qgscoordinatetransformcontext.sip.in
Expand Up @@ -221,9 +221,24 @@ be used.
an empty string, and the deprecated calculateDatumTransforms() method should be used instead.


.. warning::

Always check the result of mustReverseCoordinateOperation() in order to determine if the
proj coordinate operation string returned by this method corresponds to the reverse operation, and
must be manually flipped when calculating coordinate transforms.


.. versionadded:: 3.8
%End

bool mustReverseCoordinateOperation( const QgsCoordinateReferenceSystem &source, const QgsCoordinateReferenceSystem &destination ) const;
%Docstring
Returns ``True`` if the coordinate operation returned by calculateCoordinateOperation() for the ``source`` to ``destination`` pair
must be inverted.

.. versionadded:: 3.10.2
%End


bool readXml( const QDomElement &element, const QgsReadWriteContext &context, QStringList &missingTransforms /Out/ );
%Docstring
Expand Down
30 changes: 30 additions & 0 deletions src/core/qgscoordinatetransformcontext.cpp
Expand Up @@ -189,6 +189,36 @@ QString QgsCoordinateTransformContext::calculateCoordinateOperation( const QgsCo
#endif
}

bool QgsCoordinateTransformContext::mustReverseCoordinateOperation( const QgsCoordinateReferenceSystem &source, const QgsCoordinateReferenceSystem &destination ) const
{
#if PROJ_VERSION_MAJOR>=6
const QString srcKey = source.authid();
const QString destKey = destination.authid();

d->mLock.lockForRead();
QString res = d->mSourceDestDatumTransforms.value( qMakePair( srcKey, destKey ), QString() );
if ( !res.isEmpty() )
{
d->mLock.unlock();
return false;
}
// see if the reverse operation is present
res = d->mSourceDestDatumTransforms.value( qMakePair( destKey, srcKey ), QString() );
if ( !res.isEmpty() )
{
d->mLock.unlock();
return true;
}

d->mLock.unlock();
return false;
#else
Q_UNUSED( source )
Q_UNUSED( destination )
return false;
#endif
}

bool QgsCoordinateTransformContext::readXml( const QDomElement &element, const QgsReadWriteContext &, QStringList &missingTransforms )
{
d.detach();
Expand Down
12 changes: 12 additions & 0 deletions src/core/qgscoordinatetransformcontext.h
Expand Up @@ -206,10 +206,22 @@ class CORE_EXPORT QgsCoordinateTransformContext
* \note Requires Proj 6.0 or later. Builds based on earlier Proj versions will always return
* an empty string, and the deprecated calculateDatumTransforms() method should be used instead.
*
* \warning Always check the result of mustReverseCoordinateOperation() in order to determine if the
* proj coordinate operation string returned by this method corresponds to the reverse operation, and
* must be manually flipped when calculating coordinate transforms.
*
* \since QGIS 3.8
*/
QString calculateCoordinateOperation( const QgsCoordinateReferenceSystem &source, const QgsCoordinateReferenceSystem &destination ) const;

/**
* Returns TRUE if the coordinate operation returned by calculateCoordinateOperation() for the \a source to \a destination pair
* must be inverted.
*
* \since QGIS 3.10.2
*/
bool mustReverseCoordinateOperation( const QgsCoordinateReferenceSystem &source, const QgsCoordinateReferenceSystem &destination ) const;

// TODO QGIS 4.0 - remove missingTransforms, not used for Proj >= 6.0 builds

/**
Expand Down
28 changes: 28 additions & 0 deletions tests/src/python/test_qgscoordinatetransformcontext.py
Expand Up @@ -147,6 +147,9 @@ def testSourceDestinationDatumTransformsProj6(self):
proj_string = '+proj=pipeline +step +inv +proj=lcc +lat_0=-37 +lon_0=145 +lat_1=-36 +lat_2=-38 +x_0=2500000 +y_0=2500000 +ellps=GRS80 +step +proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1'
self.assertFalse(
context.hasTransform(QgsCoordinateReferenceSystem('EPSG:3111'), QgsCoordinateReferenceSystem('EPSG:4283')))
self.assertFalse(context.mustReverseCoordinateOperation(QgsCoordinateReferenceSystem('EPSG:3111'), QgsCoordinateReferenceSystem('EPSG:4283')))
self.assertFalse(context.mustReverseCoordinateOperation(QgsCoordinateReferenceSystem('EPSG:4283'),
QgsCoordinateReferenceSystem('EPSG:3111')))
self.assertTrue(context.addCoordinateOperation(QgsCoordinateReferenceSystem('EPSG:3111'),
QgsCoordinateReferenceSystem('EPSG:4283'), proj_string))
self.assertTrue(
Expand All @@ -156,6 +159,31 @@ def testSourceDestinationDatumTransformsProj6(self):
self.assertFalse(
context.hasTransform(QgsCoordinateReferenceSystem('EPSG:3113'), QgsCoordinateReferenceSystem('EPSG:4283')))
self.assertEqual(context.coordinateOperations(), {('EPSG:3111', 'EPSG:4283'): proj_string})
self.assertTrue(context.mustReverseCoordinateOperation(QgsCoordinateReferenceSystem('EPSG:4283'),
QgsCoordinateReferenceSystem('EPSG:3111')))

self.assertTrue(
context.hasTransform(QgsCoordinateReferenceSystem('EPSG:4283'), QgsCoordinateReferenceSystem('EPSG:3111')))

self.assertEqual(context.calculateCoordinateOperation(QgsCoordinateReferenceSystem('EPSG:3111'),
QgsCoordinateReferenceSystem('EPSG:4283')), proj_string)
self.assertFalse(context.mustReverseCoordinateOperation(QgsCoordinateReferenceSystem('EPSG:3111'),
QgsCoordinateReferenceSystem('EPSG:4283')))
# ideally not equal, but for now it's all we can do, and return True for mustReverseCoordinateOperation here
self.assertEqual(context.calculateCoordinateOperation(QgsCoordinateReferenceSystem('EPSG:4283'),
QgsCoordinateReferenceSystem('EPSG:3111')), proj_string)
self.assertTrue(context.mustReverseCoordinateOperation(QgsCoordinateReferenceSystem('EPSG:4283'),
QgsCoordinateReferenceSystem('EPSG:3111')))
proj_string_2 = 'dummy'
self.assertTrue(context.addCoordinateOperation(QgsCoordinateReferenceSystem('EPSG:4283'),
QgsCoordinateReferenceSystem('EPSG:3111'), proj_string_2))
self.assertEqual(context.calculateCoordinateOperation(QgsCoordinateReferenceSystem('EPSG:4283'),
QgsCoordinateReferenceSystem('EPSG:3111')), proj_string_2)
self.assertFalse(context.mustReverseCoordinateOperation(QgsCoordinateReferenceSystem('EPSG:4283'),
QgsCoordinateReferenceSystem('EPSG:3111')))
context.removeCoordinateOperation(QgsCoordinateReferenceSystem('EPSG:4283'),
QgsCoordinateReferenceSystem('EPSG:3111'))

proj_string_2 = '+proj=pipeline +step +inv +proj=utm +zone=56 +south +ellps=GRS80 +step +proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1'
self.assertTrue(context.addCoordinateOperation(QgsCoordinateReferenceSystem('EPSG:28356'),
QgsCoordinateReferenceSystem(4283), proj_string_2))
Expand Down

0 comments on commit a1ac778

Please sign in to comment.