Upgrade QgsCoordinateTransformContext for proj 6

This reworks (on proj 6 builds only!) QgsCoordinateTransformContext
to use proj strings of proj coordinate operations to handle the
users' preferred list of operations when transforming coordinates
between two CRSes.

Previously, the context heavily utilised internal transform ID
numbers, which were QGIS specific and relied on matching entries
from the QGIS srs.db file. This approach was undesirable because
it meant QGIS had to maintain and carry it's own table of
possible transform pathways between CRS pairs (which was difficult
to update, impossible to track, and most likely severely out of
date).

Now we can utilse Proj 6's (wonderful!) logic for determining the
best coordinate operation to utilise between two CRSes. All the
old API has been deprecated and no longer works under proj 6, but
that's unavoidable (and unlikely to be in use by plugins anyway,
it's VERY low level stuff).

A further bonus of this work is that QgsCoordinateTransform no
longer relies on proj strings of the source/dest CRS to build
the transform -- the issue with that approach was that proj
strings are lossy (and not always possible to generate), so
now by default we are generating better pathways between CRS
pairs.

This resolves issues with transforms which rely on pivot datums,
such as GDA94 - GDA2020 conversions.

Sponsored by ICSM
This commit is contained in:
Nyall Dawson 2019-05-27 13:19:53 +10:00
parent 0cf4ecf491
commit c82d3b643f
21 changed files with 985 additions and 208 deletions

View File

@ -288,7 +288,43 @@ otherwise points are transformed from destination to source CRS.
Returns ``True`` if the transform short circuits because the source and destination are equivalent.
%End
int sourceDatumTransformId() const;
QString coordinateOperation() const;
%Docstring
Returns a Proj string representing the coordinate operation which will be used to transform
coordinates.
.. note::
Requires Proj 6.0 or later. Builds based on earlier Proj versions will always return
an empty string, and the deprecated sourceDatumTransformId() or destinationDatumTransformId() methods should be used instead.
.. seealso:: :py:func:`setCoordinateOperation`
.. versionadded:: 3.8
%End
void setCoordinateOperation( const QString &operation ) const;
%Docstring
Sets a Proj string representing the coordinate ``operation`` which will be used to transform
coordinates.
.. warning::
It is the caller's responsibility to ensure that ``operation`` is a valid Proj
coordinate operation string.
.. note::
Requires Proj 6.0 or later. Builds based on earlier Proj versions will ignore this setting,
and the deprecated setSourceDatumTransformId() or setDestinationDatumTransformId() methods should be used instead.
.. seealso:: :py:func:`coordinateOperation`
.. versionadded:: 3.8
%End
int sourceDatumTransformId() const /Deprecated/;
%Docstring
Returns the ID of the datum transform to use when projecting from the source
CRS.
@ -301,9 +337,11 @@ but can be manually overwritten by a call to setSourceDatumTransformId().
.. seealso:: :py:func:`setSourceDatumTransformId`
.. seealso:: :py:func:`destinationDatumTransformId`
.. deprecated:: Unused on builds based on Proj 6.0 or later
%End
void setSourceDatumTransformId( int datumId );
void setSourceDatumTransformId( int datumId ) /Deprecated/;
%Docstring
Sets the ``datumId`` ID of the datum transform to use when projecting from the source
CRS.
@ -316,9 +354,11 @@ Calling this method will overwrite any automatically calculated datum transform.
.. seealso:: :py:func:`sourceDatumTransformId`
.. seealso:: :py:func:`setDestinationDatumTransformId`
.. deprecated:: Unused on builds based on Proj 6.0 or later
%End
int destinationDatumTransformId() const;
int destinationDatumTransformId() const /Deprecated/;
%Docstring
Returns the ID of the datum transform to use when projecting to the destination
CRS.
@ -331,9 +371,11 @@ but can be manually overwritten by a call to setDestinationDatumTransformId().
.. seealso:: :py:func:`setDestinationDatumTransformId`
.. seealso:: :py:func:`sourceDatumTransformId`
.. deprecated:: Unused on builds based on Proj 6.0 or later
%End
void setDestinationDatumTransformId( int datumId );
void setDestinationDatumTransformId( int datumId ) /Deprecated/;
%Docstring
Sets the ``datumId`` ID of the datum transform to use when projecting to the destination
CRS.
@ -346,6 +388,8 @@ Calling this method will overwrite any automatically calculated datum transform.
.. seealso:: :py:func:`destinationDatumTransformId`
.. seealso:: :py:func:`setSourceDatumTransformId`
.. deprecated:: Unused on builds based on Proj 6.0 or later
%End
static void invalidateCache();

View File

@ -80,7 +80,30 @@ required for transformations for that source or destination.
.. seealso:: :py:func:`addSourceDestinationDatumTransform`
.. deprecated:: Has no effect on builds based on Proj 6.0 or later
.. deprecated:: Has no effect on builds based on Proj 6.0 or later, use coordinateOperations() instead.
%End
QMap< QPair< QString, QString>, QString > coordinateOperations() const;
%Docstring
Returns the stored mapping for source to destination CRS pairs to associated coordinate operation to use
(as a proj string). The map keys will be :py:func:`QgsCoordinateReferenceSystems.authid()`s.
.. warning::
This method should not be used to calculate the corresponding coordinate operation
to use for a coordinate transform. Instead, always use calculateCoordinateOperation()
to determine this.
.. seealso:: :py:func:`addCoordinateOperation`
.. note::
Requires Proj 6.0 or later. Builds based on earlier Proj versions will always return an empty list,
and the deprecated sourceDestinationDatumTransforms() method must be used instead.
.. versionadded:: 3.8
%End
bool addSourceDestinationDatumTransform( const QgsCoordinateReferenceSystem &sourceCrs, const QgsCoordinateReferenceSystem &destinationCrs, int sourceTransformId, int destinationTransformId ) /Deprecated/;
@ -93,17 +116,35 @@ required for transformations for that source or destination.
Returns ``True`` if the new transform pair was added successfully.
.. note::
Transforms set using this method will override any specific source or destination
transforms set by addSourceDatumTransform() or addDestinationDatumTransform().
.. seealso:: :py:func:`sourceDestinationDatumTransforms`
.. seealso:: :py:func:`removeSourceDestinationDatumTransform`
.. deprecated:: Has no effect on builds based on Proj 6.0 or later, use addCoordinateOperation() instead.
%End
.. deprecated:: Has no effect on builds based on Proj 6.0 or later
bool addCoordinateOperation( const QgsCoordinateReferenceSystem &sourceCrs, const QgsCoordinateReferenceSystem &destinationCrs, const QString &coordinateOperationProjString );
%Docstring
Adds a new ``coordinateOperationProjString`` to use when projecting coordinates
from the specified ``sourceCrs`` to the specified ``destinationCrs``.
``coordinateOperationProjString`` should be set to a valid Proj coordinate operation
string. If ``coordinateOperationProjString`` is empty, then the default Proj operation
will be used when transforming between the coordinate reference systems.
Returns ``True`` if the new coordinate operation was added successfully.
.. seealso:: :py:func:`coordinateOperations`
.. seealso:: :py:func:`removeCoordinateOperation`
.. note::
Requires Proj 6.0 or later. Builds based on earlier Proj versions will ignore this setting,
and the deprecated addSourceDestinationDatumTransform() method must be used instead.
.. versionadded:: 3.8
%End
void removeSourceDestinationDatumTransform( const QgsCoordinateReferenceSystem &sourceCrs, const QgsCoordinateReferenceSystem &destinationCrs ) /Deprecated/;
@ -118,8 +159,7 @@ Removes the source to destination datum transform pair for the specified ``sourc
void removeCoordinateOperation( const QgsCoordinateReferenceSystem &sourceCrs, const QgsCoordinateReferenceSystem &destinationCrs );
%Docstring
Removes the source to destination datum coordinated operation for the specified ``sourceCrs`` and
``destinationCrs``.
Removes the coordinate operation for the specified ``sourceCrs`` and ``destinationCrs``.
.. versionadded:: 3.8
%End
@ -127,7 +167,7 @@ Removes the source to destination datum coordinated operation for the specified
bool hasTransform( const QgsCoordinateReferenceSystem &source,
const QgsCoordinateReferenceSystem &destination ) const;
%Docstring
Returns ``True`` if the context has a valid datum transform to use
Returns ``True`` if the context has a valid coordinate operation to use
when transforming from the specified ``source`` CRS to ``destination`` CRS.
.. note::
@ -147,7 +187,29 @@ destination.
source and destination are reversible.
.. deprecated:: Has no effect on builds based on Proj 6.0 or later
.. deprecated:: Has no effect on builds based on Proj 6.0 or later. Use calculateCoordinateOperation() instead.
%End
QString calculateCoordinateOperation( const QgsCoordinateReferenceSystem &source, const QgsCoordinateReferenceSystem &destination ) const;
%Docstring
Returns the Proj coordinate operation string to use when transforming
from the specified ``source`` CRS to ``destination`` CRS.
Returns an empty string if no specific coordinate operation is set for the source to
destination pair, in which case the default Proj coordinate operation should
be used.
.. note::
source and destination are reversible.
.. 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.
.. versionadded:: 3.8
%End
bool readXml( const QDomElement &element, const QgsReadWriteContext &context, QStringList &missingTransforms /Out/ );

View File

@ -29,9 +29,6 @@ struct QgsRasterViewPort
QgsCoordinateReferenceSystem mDestCRS;
int mSrcDatumTransform;
int mDestDatumTransform;
QgsCoordinateTransformContext mTransformContext;
};

View File

@ -76,8 +76,8 @@ class QgsCoordinateReferenceSystemPrivate : public QSharedData
, mAxisInverted( other.mAxisInverted )
{
#if PROJ_VERSION_MAJOR>=6
if ( mIsValid && mPj.get() )
mPj.reset( proj_clone( QgsProjContext::get(), mPj.get() ) );
if ( mIsValid && other.mPj )
mPj.reset( proj_clone( QgsProjContext::get(), other.mPj.get() ) );
#else
if ( mIsValid )
{

View File

@ -63,7 +63,11 @@ QgsCoordinateTransform::QgsCoordinateTransform( const QgsCoordinateReferenceSyst
if ( !d->checkValidity() )
return;
#if PROJ_VERSION_MAJOR>=6
if ( !setFromCache( d->mSourceCRS, d->mDestCRS, d->mProjCoordinateOperation ) )
#else
if ( !setFromCache( d->mSourceCRS, d->mDestCRS, d->mSourceDatumTransform, d->mDestinationDatumTransform ) )
#endif
{
d->initialize();
addToCache();
@ -82,7 +86,11 @@ QgsCoordinateTransform::QgsCoordinateTransform( const QgsCoordinateReferenceSyst
if ( !d->checkValidity() )
return;
#if PROJ_VERSION_MAJOR>=6
if ( !setFromCache( d->mSourceCRS, d->mDestCRS, d->mProjCoordinateOperation ) )
#else
if ( !setFromCache( d->mSourceCRS, d->mDestCRS, d->mSourceDatumTransform, d->mDestinationDatumTransform ) )
#endif
{
d->initialize();
addToCache();
@ -99,7 +107,11 @@ QgsCoordinateTransform::QgsCoordinateTransform( const QgsCoordinateReferenceSyst
if ( !d->checkValidity() )
return;
#if PROJ_VERSION_MAJOR>=6
if ( !setFromCache( d->mSourceCRS, d->mDestCRS, d->mProjCoordinateOperation ) )
#else
if ( !setFromCache( d->mSourceCRS, d->mDestCRS, d->mSourceDatumTransform, d->mDestinationDatumTransform ) )
#endif
{
d->initialize();
addToCache();
@ -135,7 +147,11 @@ void QgsCoordinateTransform::setSourceCrs( const QgsCoordinateReferenceSystem &c
return;
d->calculateTransforms( mContext );
#if PROJ_VERSION_MAJOR>=6
if ( !setFromCache( d->mSourceCRS, d->mDestCRS, d->mProjCoordinateOperation ) )
#else
if ( !setFromCache( d->mSourceCRS, d->mDestCRS, d->mSourceDatumTransform, d->mDestinationDatumTransform ) )
#endif
{
d->initialize();
addToCache();
@ -149,7 +165,11 @@ void QgsCoordinateTransform::setDestinationCrs( const QgsCoordinateReferenceSyst
return;
d->calculateTransforms( mContext );
#if PROJ_VERSION_MAJOR>=6
if ( !setFromCache( d->mSourceCRS, d->mDestCRS, d->mProjCoordinateOperation ) )
#else
if ( !setFromCache( d->mSourceCRS, d->mDestCRS, d->mSourceDatumTransform, d->mDestinationDatumTransform ) )
#endif
{
d->initialize();
addToCache();
@ -167,7 +187,11 @@ void QgsCoordinateTransform::setContext( const QgsCoordinateTransformContext &co
return;
d->calculateTransforms( mContext );
#if PROJ_VERSION_MAJOR>=6
if ( !setFromCache( d->mSourceCRS, d->mDestCRS, d->mProjCoordinateOperation ) )
#else
if ( !setFromCache( d->mSourceCRS, d->mDestCRS, d->mSourceDatumTransform, d->mDestinationDatumTransform ) )
#endif
{
d->initialize();
addToCache();
@ -757,6 +781,17 @@ bool QgsCoordinateTransform::isShortCircuited() const
return !d->mIsValid || d->mShortCircuit;
}
QString QgsCoordinateTransform::coordinateOperation() const
{
return d->mProjCoordinateOperation;
}
void QgsCoordinateTransform::setCoordinateOperation( const QString &operation ) const
{
d.detach();
d->mProjCoordinateOperation = operation;
}
const char *finder( const char *name )
{
QString proj;
@ -769,6 +804,46 @@ const char *finder( const char *name )
return proj.toUtf8();
}
#if PROJ_VERSION_MAJOR>=6
bool QgsCoordinateTransform::setFromCache( const QgsCoordinateReferenceSystem &src, const QgsCoordinateReferenceSystem &dest, const QString &coordinateOperationProj )
{
if ( !src.isValid() || !dest.isValid() )
return false;
const QString sourceKey = src.authid().isEmpty() ?
src.toWkt() : src.authid();
const QString destKey = dest.authid().isEmpty() ?
dest.toWkt() : dest.authid();
if ( sourceKey.isEmpty() || destKey.isEmpty() )
return false;
sCacheLock.lockForRead();
const QList< QgsCoordinateTransform > values = sTransforms.values( qMakePair( src.authid(), dest.authid() ) );
for ( auto valIt = values.constBegin(); valIt != values.constEnd(); ++valIt )
{
if ( ( *valIt ).coordinateOperation() == coordinateOperationProj )
{
// need to save, and then restore the context... we don't want this to be cached or to use the values from the cache
QgsCoordinateTransformContext context = mContext;
#ifdef QGISDEBUG
bool hasContext = mHasContext;
#endif
*this = *valIt;
sCacheLock.unlock();
mContext = context;
#ifdef QGISDEBUG
mHasContext = hasContext;
#endif
return true;
}
}
sCacheLock.unlock();
return false;
}
#else
bool QgsCoordinateTransform::setFromCache( const QgsCoordinateReferenceSystem &src, const QgsCoordinateReferenceSystem &dest, int srcDatumTransform, int destDatumTransform )
{
if ( !src.isValid() || !dest.isValid() )
@ -808,6 +883,7 @@ bool QgsCoordinateTransform::setFromCache( const QgsCoordinateReferenceSystem &s
sCacheLock.unlock();
return false;
}
#endif
void QgsCoordinateTransform::addToCache()
{
@ -829,24 +905,32 @@ void QgsCoordinateTransform::addToCache()
int QgsCoordinateTransform::sourceDatumTransformId() const
{
Q_NOWARN_DEPRECATED_PUSH
return d->mSourceDatumTransform;
Q_NOWARN_DEPRECATED_POP
}
void QgsCoordinateTransform::setSourceDatumTransformId( int dt )
{
d.detach();
Q_NOWARN_DEPRECATED_PUSH
d->mSourceDatumTransform = dt;
Q_NOWARN_DEPRECATED_POP
}
int QgsCoordinateTransform::destinationDatumTransformId() const
{
Q_NOWARN_DEPRECATED_PUSH
return d->mDestinationDatumTransform;
Q_NOWARN_DEPRECATED_POP
}
void QgsCoordinateTransform::setDestinationDatumTransformId( int dt )
{
d.detach();
Q_NOWARN_DEPRECATED_PUSH
d->mDestinationDatumTransform = dt;
Q_NOWARN_DEPRECATED_POP
}
void QgsCoordinateTransform::invalidateCache()

View File

@ -332,6 +332,33 @@ class CORE_EXPORT QgsCoordinateTransform
*/
bool isShortCircuited() const;
/**
* Returns a Proj string representing the coordinate operation which will be used to transform
* coordinates.
*
* \note Requires Proj 6.0 or later. Builds based on earlier Proj versions will always return
* an empty string, and the deprecated sourceDatumTransformId() or destinationDatumTransformId() methods should be used instead.
*
* \see setCoordinateOperation()
* \since QGIS 3.8
*/
QString coordinateOperation() const;
/**
* Sets a Proj string representing the coordinate \a operation which will be used to transform
* coordinates.
*
* \warning It is the caller's responsibility to ensure that \a operation is a valid Proj
* coordinate operation string.
*
* \note Requires Proj 6.0 or later. Builds based on earlier Proj versions will ignore this setting,
* and the deprecated setSourceDatumTransformId() or setDestinationDatumTransformId() methods should be used instead.
*
* \see coordinateOperation()
* \since QGIS 3.8
*/
void setCoordinateOperation( const QString &operation ) const;
/**
* Returns the ID of the datum transform to use when projecting from the source
* CRS.
@ -342,8 +369,10 @@ class CORE_EXPORT QgsCoordinateTransform
* \see QgsDatumTransform
* \see setSourceDatumTransformId()
* \see destinationDatumTransformId()
*
* \deprecated Unused on builds based on Proj 6.0 or later
*/
int sourceDatumTransformId() const;
Q_DECL_DEPRECATED int sourceDatumTransformId() const SIP_DEPRECATED;
/**
* Sets the \a datumId ID of the datum transform to use when projecting from the source
@ -355,8 +384,10 @@ class CORE_EXPORT QgsCoordinateTransform
* \see QgsDatumTransform
* \see sourceDatumTransformId()
* \see setDestinationDatumTransformId()
*
* \deprecated Unused on builds based on Proj 6.0 or later
*/
void setSourceDatumTransformId( int datumId );
Q_DECL_DEPRECATED void setSourceDatumTransformId( int datumId ) SIP_DEPRECATED;
/**
* Returns the ID of the datum transform to use when projecting to the destination
@ -368,8 +399,10 @@ class CORE_EXPORT QgsCoordinateTransform
* \see QgsDatumTransform
* \see setDestinationDatumTransformId()
* \see sourceDatumTransformId()
*
* \deprecated Unused on builds based on Proj 6.0 or later
*/
int destinationDatumTransformId() const;
Q_DECL_DEPRECATED int destinationDatumTransformId() const SIP_DEPRECATED;
/**
* Sets the \a datumId ID of the datum transform to use when projecting to the destination
@ -381,8 +414,10 @@ class CORE_EXPORT QgsCoordinateTransform
* \see QgsDatumTransform
* \see destinationDatumTransformId()
* \see setSourceDatumTransformId()
*
* \deprecated Unused on builds based on Proj 6.0 or later
*/
void setDestinationDatumTransformId( int datumId );
Q_DECL_DEPRECATED void setDestinationDatumTransformId( int datumId ) SIP_DEPRECATED;
/**
* Clears the internal cache used to initialize QgsCoordinateTransform objects.
@ -414,10 +449,16 @@ class CORE_EXPORT QgsCoordinateTransform
bool mHasContext = false;
#endif
#if PROJ_VERSION_MAJOR>=6
bool setFromCache( const QgsCoordinateReferenceSystem &src,
const QgsCoordinateReferenceSystem &dest,
const QString &coordinateOperationProj );
#else
bool setFromCache( const QgsCoordinateReferenceSystem &src,
const QgsCoordinateReferenceSystem &dest,
int srcDatumTransform,
int destDatumTransform );
#endif
void addToCache();
// cache

View File

@ -52,11 +52,14 @@ QgsProjContextStore::~QgsProjContextStore()
#endif
Q_NOWARN_DEPRECATED_PUSH // because of deprecated members
QgsCoordinateTransformPrivate::QgsCoordinateTransformPrivate()
{
setFinder();
}
Q_NOWARN_DEPRECATED_POP
Q_NOWARN_DEPRECATED_PUSH // because of deprecated members
QgsCoordinateTransformPrivate::QgsCoordinateTransformPrivate( const QgsCoordinateReferenceSystem &source,
const QgsCoordinateReferenceSystem &destination,
const QgsCoordinateTransformContext &context )
@ -66,7 +69,9 @@ QgsCoordinateTransformPrivate::QgsCoordinateTransformPrivate( const QgsCoordinat
setFinder();
calculateTransforms( context );
}
Q_NOWARN_DEPRECATED_POP
Q_NOWARN_DEPRECATED_PUSH // because of deprecated members
QgsCoordinateTransformPrivate::QgsCoordinateTransformPrivate( const QgsCoordinateReferenceSystem &source, const QgsCoordinateReferenceSystem &destination, int sourceDatumTransform, int destDatumTransform )
: mSourceCRS( source )
, mDestCRS( destination )
@ -90,12 +95,15 @@ QgsCoordinateTransformPrivate::QgsCoordinateTransformPrivate( const QgsCoordinat
//must reinitialize to setup mSourceProjection and mDestinationProjection
initialize();
}
Q_NOWARN_DEPRECATED_POP
Q_NOWARN_DEPRECATED_PUSH
QgsCoordinateTransformPrivate::~QgsCoordinateTransformPrivate()
{
// free the proj objects
freeProj();
}
Q_NOWARN_DEPRECATED_POP
bool QgsCoordinateTransformPrivate::checkValidity()
{
@ -135,14 +143,15 @@ bool QgsCoordinateTransformPrivate::initialize()
mIsValid = true;
// init the projections (destination and source)
freeProj();
#if PROJ_VERSION_MAJOR < 6
Q_NOWARN_DEPRECATED_PUSH
int sourceDatumTransform = mSourceDatumTransform;
int destDatumTransform = mDestinationDatumTransform;
bool useDefaultDatumTransform = ( sourceDatumTransform == - 1 && destDatumTransform == -1 );
// init the projections (destination and source)
freeProj();
Q_NOWARN_DEPRECATED_PUSH
mSourceProjString = mSourceCRS.toProj4();
if ( !useDefaultDatumTransform )
{
@ -162,12 +171,13 @@ bool QgsCoordinateTransformPrivate::initialize()
{
mDestProjString += ( ' ' + QgsDatumTransform::datumTransformToProj( destDatumTransform ) );
}
Q_NOWARN_DEPRECATED_POP
if ( !useDefaultDatumTransform )
{
addNullGridShifts( mSourceProjString, mDestProjString, sourceDatumTransform, destDatumTransform );
}
Q_NOWARN_DEPRECATED_POP
#endif
// create proj projections for current thread
ProjData res = threadLocalProjData();
@ -244,11 +254,15 @@ bool QgsCoordinateTransformPrivate::initialize()
void QgsCoordinateTransformPrivate::calculateTransforms( const QgsCoordinateTransformContext &context )
{
// recalculate datum transforms from context
#if PROJ_VERSION_MAJOR >= 6
mProjCoordinateOperation = context.calculateCoordinateOperation( mSourceCRS, mDestCRS );
#else
Q_NOWARN_DEPRECATED_PUSH
QgsDatumTransform::TransformPair transforms = context.calculateDatumTransforms( mSourceCRS, mDestCRS );
Q_NOWARN_DEPRECATED_POP
mSourceDatumTransform = transforms.sourceTransformId;
mDestinationDatumTransform = transforms.destinationTransformId;
Q_NOWARN_DEPRECATED_POP
#endif
}
ProjData QgsCoordinateTransformPrivate::threadLocalProjData()
@ -287,7 +301,30 @@ ProjData QgsCoordinateTransformPrivate::threadLocalProjData()
#if PROJ_VERSION_MAJOR>=6
#if PROJ_VERSION_MINOR>=1
QgsProjUtils::proj_pj_unique_ptr transform( proj_create_crs_to_crs( context, mSourceProjString.toUtf8().constData(), mDestProjString.toUtf8().constData(), nullptr ) );
QgsProjUtils::proj_pj_unique_ptr transform;
if ( !mProjCoordinateOperation.isEmpty() )
transform.reset( proj_create( context, mProjCoordinateOperation.toUtf8().constData() ) );
if ( !transform ) // fallback on default proj pathway
{
if ( !mSourceCRS.projObject() || ! mDestCRS.projObject() )
return nullptr;
PJ_OPERATION_FACTORY_CONTEXT *operationContext = proj_create_operation_factory_context( context, nullptr );
// See https://lists.osgeo.org/pipermail/proj/2019-May/008604.html
proj_operation_factory_context_set_spatial_criterion( context, operationContext, PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION );
if ( PJ_OBJ_LIST *ops = proj_create_operations( context, mSourceCRS.projObject(), mDestCRS.projObject(), operationContext ) )
{
int count = proj_list_get_count( ops );
if ( count > 0 )
transform.reset( proj_list_get( context, ops, 0 ) );
proj_list_destroy( ops );
}
proj_operation_factory_context_destroy( operationContext );
}
if ( !transform )
{
// ouch!
@ -317,6 +354,7 @@ ProjData QgsCoordinateTransformPrivate::threadLocalProjData()
return res;
}
#if PROJ_VERSION_MAJOR<6
QString QgsCoordinateTransformPrivate::stripDatumTransform( const QString &proj4 ) const
{
QStringList parameterSplit = proj4.split( '+', QString::SkipEmptyParts );
@ -363,6 +401,7 @@ void QgsCoordinateTransformPrivate::addNullGridShifts( QString &srcProjString, Q
destProjString += QLatin1String( " +nadgrids=@null" );
}
}
#endif
void QgsCoordinateTransformPrivate::setFinder()
{

View File

@ -120,11 +120,12 @@ class QgsCoordinateTransformPrivate : public QSharedData
//! QgsCoordinateReferenceSystem of the destination (map canvas) coordinate system
QgsCoordinateReferenceSystem mDestCRS;
QString mSourceProjString;
QString mDestProjString;
Q_DECL_DEPRECATED QString mSourceProjString;
Q_DECL_DEPRECATED QString mDestProjString;
int mSourceDatumTransform = -1;
int mDestinationDatumTransform = -1;
Q_DECL_DEPRECATED int mSourceDatumTransform = -1;
Q_DECL_DEPRECATED int mDestinationDatumTransform = -1;
QString mProjCoordinateOperation;
bool mSourceAxisOrderSwapped = false;
bool mDestAxisOrderSwapped = false;
@ -148,11 +149,13 @@ class QgsCoordinateTransformPrivate : public QSharedData
private:
#if PROJ_VERSION_MAJOR<6
//! Removes +nadgrids and +towgs84 from proj4 string
QString stripDatumTransform( const QString &proj4 ) const;
Q_DECL_DEPRECATED QString stripDatumTransform( const QString &proj4 ) const;
//! In certain situations, null grid shifts have to be added to src / dst proj string
void addNullGridShifts( QString &srcProjString, QString &destProjString, int sourceDatumTransform, int destinationDatumTransform ) const;
Q_DECL_DEPRECATED void addNullGridShifts( QString &srcProjString, QString &destProjString, int sourceDatumTransform, int destinationDatumTransform ) const;
#endif
void setFinder();

View File

@ -19,6 +19,8 @@
#include "qgscoordinatetransformcontext_p.h"
#include "qgscoordinatetransform.h"
#include "qgssettings.h"
#include "qgsprojutils.h"
QgsCoordinateTransformContext::QgsCoordinateTransformContext()
: d( new QgsCoordinateTransformContextPrivate() )
@ -60,23 +62,62 @@ void QgsCoordinateTransformContext::clear()
QMap<QPair<QString, QString>, QgsDatumTransform::TransformPair> QgsCoordinateTransformContext::sourceDestinationDatumTransforms() const
{
#if PROJ_VERSION_MAJOR>=6
return QMap<QPair<QString, QString>, QgsDatumTransform::TransformPair>();
#else
d->mLock.lockForRead();
auto res = d->mSourceDestDatumTransforms;
res.detach();
d->mLock.unlock();
return res;
#endif
}
QMap<QPair<QString, QString>, QString> QgsCoordinateTransformContext::coordinateOperations() const
{
#if PROJ_VERSION_MAJOR>=6
d->mLock.lockForRead();
auto res = d->mSourceDestDatumTransforms;
res.detach();
d->mLock.unlock();
return res;
#else
return QMap<QPair<QString, QString>, QgsDatumTransform::TransformDetails>();
#endif
}
bool QgsCoordinateTransformContext::addSourceDestinationDatumTransform( const QgsCoordinateReferenceSystem &sourceCrs, const QgsCoordinateReferenceSystem &destinationCrs, int sourceTransform, int destinationTransform )
{
if ( !sourceCrs.isValid() || !destinationCrs.isValid() )
return false;
#if PROJ_VERSION_MAJOR>=6
Q_UNUSED( sourceTransform )
Q_UNUSED( destinationTransform )
return false;
#else
d.detach();
d->mLock.lockForWrite();
d->mSourceDestDatumTransforms.insert( qMakePair( sourceCrs.authid(), destinationCrs.authid() ), QgsDatumTransform::TransformPair( sourceTransform, destinationTransform ) );
d->mLock.unlock();
return true;
#endif
}
bool QgsCoordinateTransformContext::addCoordinateOperation( const QgsCoordinateReferenceSystem &sourceCrs, const QgsCoordinateReferenceSystem &destinationCrs, const QString &coordinateOperationProjString )
{
if ( !sourceCrs.isValid() || !destinationCrs.isValid() )
return false;
#if PROJ_VERSION_MAJOR>=6
d.detach();
d->mLock.lockForWrite();
d->mSourceDestDatumTransforms.insert( qMakePair( sourceCrs.authid(), destinationCrs.authid() ), coordinateOperationProjString );
d->mLock.unlock();
return true;
#else
Q_UNUSED( sourceTransform )
Q_UNUSED( destinationTransform )
return false;
#endif
}
void QgsCoordinateTransformContext::removeSourceDestinationDatumTransform( const QgsCoordinateReferenceSystem &sourceCrs, const QgsCoordinateReferenceSystem &destinationCrs )
@ -91,15 +132,25 @@ void QgsCoordinateTransformContext::removeCoordinateOperation( const QgsCoordina
bool QgsCoordinateTransformContext::hasTransform( const QgsCoordinateReferenceSystem &source, const QgsCoordinateReferenceSystem &destination ) const
{
#if PROJ_VERSION_MAJOR>=6
const QString t = calculateCoordinateOperation( source, destination );
return !t.isEmpty();
#else
Q_NOWARN_DEPRECATED_PUSH
QgsDatumTransform::TransformPair t = calculateDatumTransforms( source, destination );
Q_NOWARN_DEPRECATED_POP
// calculateDatumTransforms already takes care of switching source and destination
return t.sourceTransformId != -1 || t.destinationTransformId != -1;
#endif
}
QgsDatumTransform::TransformPair QgsCoordinateTransformContext::calculateDatumTransforms( const QgsCoordinateReferenceSystem &source, const QgsCoordinateReferenceSystem &destination ) const
{
#if PROJ_VERSION_MAJOR>=6
Q_UNUSED( source )
Q_UNUSED( destination )
return QgsDatumTransform::TransformPair( -1, -1 );
#else
QString srcKey = source.authid();
QString destKey = destination.authid();
@ -114,6 +165,29 @@ QgsDatumTransform::TransformPair QgsCoordinateTransformContext::calculateDatumTr
}
d->mLock.unlock();
return res;
#endif
}
QString QgsCoordinateTransformContext::calculateCoordinateOperation( 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() )
{
// try to reverse
res = d->mSourceDestDatumTransforms.value( qMakePair( destKey, srcKey ), QString() );
}
d->mLock.unlock();
return res;
#else
Q_UNUSED( source )
Q_UNUSED( destination )
return QString();
#endif
}
bool QgsCoordinateTransformContext::readXml( const QDomElement &element, const QgsReadWriteContext &, QStringList &missingTransforms )
@ -140,9 +214,23 @@ bool QgsCoordinateTransformContext::readXml( const QDomElement &element, const Q
for ( int i = 0; i < srcDestNodes.size(); ++i )
{
const QDomElement transformElem = srcDestNodes.at( i ).toElement();
QString key1 = transformElem.attribute( QStringLiteral( "source" ) );
QString key2 = transformElem.attribute( QStringLiteral( "dest" ) );
const QString key1 = transformElem.attribute( QStringLiteral( "source" ) );
const QString key2 = transformElem.attribute( QStringLiteral( "dest" ) );
#if PROJ_VERSION_MAJOR>=6
const QString coordinateOp = transformElem.attribute( QStringLiteral( "coordinateOp" ) );
// try to instantiate operation, and check for missing grids
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!
result = false;
}
d->mSourceDestDatumTransforms.insert( qMakePair( key1, key2 ), coordinateOp );
#else
QString value1 = transformElem.attribute( QStringLiteral( "sourceTransform" ) );
QString value2 = transformElem.attribute( QStringLiteral( "destTransform" ) );
@ -171,6 +259,7 @@ bool QgsCoordinateTransformContext::readXml( const QDomElement &element, const Q
Q_NOWARN_DEPRECATED_POP
d->mSourceDestDatumTransforms.insert( qMakePair( key1, key2 ), QgsDatumTransform::TransformPair( datumId1, datumId2 ) );
#endif
}
d->mLock.unlock();
@ -189,10 +278,14 @@ void QgsCoordinateTransformContext::writeXml( QDomElement &element, const QgsRea
QDomElement transformElem = element.ownerDocument().createElement( QStringLiteral( "srcDest" ) );
transformElem.setAttribute( QStringLiteral( "source" ), it.key().first );
transformElem.setAttribute( QStringLiteral( "dest" ), it.key().second );
#if PROJ_VERSION_MAJOR>=6
transformElem.setAttribute( QStringLiteral( "coordinateOp" ), it.value() );
#else
Q_NOWARN_DEPRECATED_PUSH
transformElem.setAttribute( QStringLiteral( "sourceTransform" ), it.value().sourceTransformId < 0 ? QString() : QgsDatumTransform::datumTransformToProj( it.value().sourceTransformId ) );
transformElem.setAttribute( QStringLiteral( "destTransform" ), it.value().destinationTransformId < 0 ? QString() : QgsDatumTransform::datumTransformToProj( it.value().destinationTransformId ) );
Q_NOWARN_DEPRECATED_POP
#endif
contextElem.appendChild( transformElem );
}
@ -212,10 +305,32 @@ void QgsCoordinateTransformContext::readSettings()
QStringList projectionKeys = settings.allKeys();
//collect src and dest entries that belong together
#if PROJ_VERSION_MAJOR>=6
QMap< QPair< QString, QString >, QString > transforms;
#else
QMap< QPair< QString, QString >, QPair< int, int > > transforms;
#endif
QStringList::const_iterator pkeyIt = projectionKeys.constBegin();
for ( ; pkeyIt != projectionKeys.constEnd(); ++pkeyIt )
{
#if PROJ_VERSION_MAJOR>=6
if ( pkeyIt->contains( QLatin1String( "coordinateOp" ) ) )
{
QStringList split = pkeyIt->split( '/' );
QString srcAuthId, destAuthId;
if ( ! split.isEmpty() )
{
srcAuthId = split.at( 0 );
}
if ( split.size() > 1 )
{
destAuthId = split.at( 1 ).split( '_' ).at( 0 );
}
const QString proj = settings.value( *pkeyIt ).toString();
transforms[ qMakePair( srcAuthId, destAuthId )] = proj;
}
#else
if ( pkeyIt->contains( QLatin1String( "srcTransform" ) ) || pkeyIt->contains( QLatin1String( "destTransform" ) ) )
{
QStringList split = pkeyIt->split( '/' );
@ -242,13 +357,18 @@ void QgsCoordinateTransformContext::readSettings()
transforms[ qMakePair( srcAuthId, destAuthId )].second = datumId;
}
}
#endif
}
// add transforms to context
QMap< QPair< QString, QString >, QPair< int, int > >::const_iterator transformIt = transforms.constBegin();
auto transformIt = transforms.constBegin();
for ( ; transformIt != transforms.constEnd(); ++transformIt )
{
#if PROJ_VERSION_MAJOR>=6
d->mSourceDestDatumTransforms.insert( transformIt.key(), transformIt.value() );
#else
d->mSourceDestDatumTransforms.insert( transformIt.key(), QgsDatumTransform::TransformPair( transformIt.value().first, transformIt.value().second ) );
#endif
}
d->mLock.unlock();
@ -263,7 +383,7 @@ void QgsCoordinateTransformContext::writeSettings()
QStringList::const_iterator groupKeyIt = groupKeys.constBegin();
for ( ; groupKeyIt != groupKeys.constEnd(); ++groupKeyIt )
{
if ( groupKeyIt->contains( QLatin1String( "srcTransform" ) ) || groupKeyIt->contains( QLatin1String( "destTransform" ) ) )
if ( groupKeyIt->contains( QLatin1String( "srcTransform" ) ) || groupKeyIt->contains( QLatin1String( "destTransform" ) ) || groupKeyIt->contains( QLatin1String( "coordinateOp" ) ) )
{
settings.remove( *groupKeyIt );
}
@ -271,8 +391,13 @@ void QgsCoordinateTransformContext::writeSettings()
for ( auto transformIt = d->mSourceDestDatumTransforms.constBegin(); transformIt != d->mSourceDestDatumTransforms.constEnd(); ++transformIt )
{
QString srcAuthId = transformIt.key().first;
QString destAuthId = transformIt.key().second;
const QString srcAuthId = transformIt.key().first;
const QString destAuthId = transformIt.key().second;
#if PROJ_VERSION_MAJOR>=6
const QString proj = transformIt.value();
settings.setValue( srcAuthId + "//" + destAuthId + "_coordinateOp", proj );
#else
int sourceDatumTransform = transformIt.value().sourceTransformId;
QString sourceDatumProj;
Q_NOWARN_DEPRECATED_PUSH
@ -286,6 +411,7 @@ void QgsCoordinateTransformContext::writeSettings()
settings.setValue( srcAuthId + "//" + destAuthId + "_srcTransform", sourceDatumProj );
settings.setValue( srcAuthId + "//" + destAuthId + "_destTransform", destinationDatumProj );
#endif
}
settings.endGroup();

View File

@ -95,10 +95,27 @@ class CORE_EXPORT QgsCoordinateTransformContext
*
* \see addSourceDestinationDatumTransform()
*
* \deprecated Has no effect on builds based on Proj 6.0 or later
* \deprecated Has no effect on builds based on Proj 6.0 or later, use coordinateOperations() instead.
*/
Q_DECL_DEPRECATED QMap< QPair< QString, QString>, QgsDatumTransform::TransformPair > sourceDestinationDatumTransforms() const SIP_DEPRECATED;
/**
* Returns the stored mapping for source to destination CRS pairs to associated coordinate operation to use
* (as a proj string). The map keys will be QgsCoordinateReferenceSystems::authid()s.
*
* \warning This method should not be used to calculate the corresponding coordinate operation
* to use for a coordinate transform. Instead, always use calculateCoordinateOperation()
* to determine this.
*
* \see addCoordinateOperation()
*
* \note Requires Proj 6.0 or later. Builds based on earlier Proj versions will always return an empty list,
* and the deprecated sourceDestinationDatumTransforms() method must be used instead.
*
* \since QGIS 3.8
*/
QMap< QPair< QString, QString>, QString > coordinateOperations() const;
/**
* Adds a new \a sourceTransform and \a destinationTransform to use when projecting coordinates
* from the specified \a sourceCrs to the specified \a destinationCrs.
@ -108,16 +125,33 @@ class CORE_EXPORT QgsCoordinateTransformContext
*
* Returns TRUE if the new transform pair was added successfully.
*
* \note Transforms set using this method will override any specific source or destination
* transforms set by addSourceDatumTransform() or addDestinationDatumTransform().
*
* \see sourceDestinationDatumTransforms()
* \see removeSourceDestinationDatumTransform()
*
* \deprecated Has no effect on builds based on Proj 6.0 or later
* \deprecated Has no effect on builds based on Proj 6.0 or later, use addCoordinateOperation() instead.
*/
Q_DECL_DEPRECATED bool addSourceDestinationDatumTransform( const QgsCoordinateReferenceSystem &sourceCrs, const QgsCoordinateReferenceSystem &destinationCrs, int sourceTransformId, int destinationTransformId ) SIP_DEPRECATED;
/**
* Adds a new \a coordinateOperationProjString to use when projecting coordinates
* from the specified \a sourceCrs to the specified \a destinationCrs.
*
* \a coordinateOperationProjString should be set to a valid Proj coordinate operation
* string. If \a coordinateOperationProjString is empty, then the default Proj operation
* will be used when transforming between the coordinate reference systems.
*
* Returns TRUE if the new coordinate operation was added successfully.
*
* \see coordinateOperations()
* \see removeCoordinateOperation()
*
* \note Requires Proj 6.0 or later. Builds based on earlier Proj versions will ignore this setting,
* and the deprecated addSourceDestinationDatumTransform() method must be used instead.
*
* \since QGIS 3.8
*/
bool addCoordinateOperation( const QgsCoordinateReferenceSystem &sourceCrs, const QgsCoordinateReferenceSystem &destinationCrs, const QString &coordinateOperationProjString );
/**
* Removes the source to destination datum transform pair for the specified \a sourceCrs and
* \a destinationCrs.
@ -128,15 +162,14 @@ class CORE_EXPORT QgsCoordinateTransformContext
Q_DECL_DEPRECATED void removeSourceDestinationDatumTransform( const QgsCoordinateReferenceSystem &sourceCrs, const QgsCoordinateReferenceSystem &destinationCrs ) SIP_DEPRECATED ;
/**
* Removes the source to destination datum coordinated operation for the specified \a sourceCrs and
* \a destinationCrs.
* Removes the coordinate operation for the specified \a sourceCrs and \a destinationCrs.
*
* \since QGIS 3.8
*/
void removeCoordinateOperation( const QgsCoordinateReferenceSystem &sourceCrs, const QgsCoordinateReferenceSystem &destinationCrs );
/**
* Returns TRUE if the context has a valid datum transform to use
* Returns TRUE if the context has a valid coordinate operation to use
* when transforming from the specified \a source CRS to \a destination CRS.
* \note source and destination are reversible.
*/
@ -151,10 +184,27 @@ class CORE_EXPORT QgsCoordinateTransformContext
* destination.
*
* \note source and destination are reversible.
* \deprecated Has no effect on builds based on Proj 6.0 or later
* \deprecated Has no effect on builds based on Proj 6.0 or later. Use calculateCoordinateOperation() instead.
*/
Q_DECL_DEPRECATED QgsDatumTransform::TransformPair calculateDatumTransforms( const QgsCoordinateReferenceSystem &source, const QgsCoordinateReferenceSystem &destination ) const SIP_DEPRECATED;
/**
* Returns the Proj coordinate operation string to use when transforming
* from the specified \a source CRS to \a destination CRS.
*
* Returns an empty string if no specific coordinate operation is set for the source to
* destination pair, in which case the default Proj coordinate operation should
* be used.
*
* \note source and destination are reversible.
*
* \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.
*
* \since QGIS 3.8
*/
QString calculateCoordinateOperation( const QgsCoordinateReferenceSystem &source, const QgsCoordinateReferenceSystem &destination ) const;
/**
* Reads the context's state from a DOM \a element.
*

View File

@ -52,25 +52,16 @@ class QgsCoordinateTransformContextPrivate : public QSharedData
{
other.mLock.lockForRead();
mSourceDestDatumTransforms = other.mSourceDestDatumTransforms;
#if 0
mSourceDatumTransforms = other.mSourceDatumTransforms;
mDestDatumTransforms = other.mDestDatumTransforms;
#endif
other.mLock.unlock();
}
/**
* Mapping for datum transforms to use for source/destination CRS pairs.
* Matching records from this map will take precedence over other transform maps.
* Mapping for coordinate operation Proj string to use for source/destination CRS pairs.
*/
#if PROJ_VERSION_MAJOR>=6
QMap< QPair< QString, QString >, QString > mSourceDestDatumTransforms;
#else
QMap< QPair< QString, QString >, QgsDatumTransform::TransformPair > mSourceDestDatumTransforms;
#if 0
//! Mapping for datum transforms to use for source CRS
QMap< QString, int > mSourceDatumTransforms;
//! Mapping for datum transforms to use for destination CRS
QMap< QString, int > mDestDatumTransforms;
#endif
//! Mutex for making QgsCoordinateTransformContextPrivate thread safe

View File

@ -143,6 +143,8 @@ class CORE_EXPORT QgsDatumTransform
/**
* Contains information about a coordinate transformation operation.
*
* \note Only used in builds based on on Proj >= 6.0
* \since QGIS 3.8
*/
struct TransformDetails

View File

@ -376,11 +376,11 @@ QList<QgsEllipsoidUtils::EllipsoidDefinition> QgsEllipsoidUtils::definitions()
def.parameters.semiMinor = semiMinor;
def.parameters.inverseFlattening = invFlattening;
if ( !semiMinorComputed )
def.parameters.crs = QgsCoordinateReferenceSystem::fromProj4( QStringLiteral( "+proj=longlat +a=%1 +b=%2 +no_defs" ).arg( def.parameters.semiMajor, 0, 'g', 17 ).arg( def.parameters.semiMinor, 0, 'g', 17 ) );
def.parameters.crs = QgsCoordinateReferenceSystem::fromProj4( QStringLiteral( "+proj=longlat +a=%1 +b=%2 +no_defs +type=crs" ).arg( def.parameters.semiMajor, 0, 'g', 17 ).arg( def.parameters.semiMinor, 0, 'g', 17 ) );
else if ( !qgsDoubleNear( def.parameters.inverseFlattening, 0.0 ) )
def.parameters.crs = QgsCoordinateReferenceSystem::fromProj4( QStringLiteral( "+proj=longlat +a=%1 +rf=%2 +no_defs" ).arg( def.parameters.semiMajor, 0, 'g', 17 ).arg( def.parameters.inverseFlattening, 0, 'g', 17 ) );
def.parameters.crs = QgsCoordinateReferenceSystem::fromProj4( QStringLiteral( "+proj=longlat +a=%1 +rf=%2 +no_defs +type=crs" ).arg( def.parameters.semiMajor, 0, 'g', 17 ).arg( def.parameters.inverseFlattening, 0, 'g', 17 ) );
else
def.parameters.crs = QgsCoordinateReferenceSystem::fromProj4( QStringLiteral( "+proj=longlat +a=%1 +no_defs" ).arg( def.parameters.semiMajor, 0, 'g', 17 ) );
def.parameters.crs = QgsCoordinateReferenceSystem::fromProj4( QStringLiteral( "+proj=longlat +a=%1 +no_defs +type=crs" ).arg( def.parameters.semiMajor, 0, 'g', 17 ) );
}
else
{

View File

@ -176,5 +176,42 @@ QgsProjUtils::proj_pj_unique_ptr QgsProjUtils::crsToSingleCrs( const PJ *crs )
return nullptr;
}
bool QgsProjUtils::coordinateOperationIsAvailable( const QString &projDef )
{
if ( projDef.isEmpty() )
return true;
PJ_CONTEXT *context = QgsProjContext::get();
QgsProjUtils::proj_pj_unique_ptr coordinateOperation( proj_create( context, projDef.toUtf8().constData() ) );
if ( !coordinateOperation )
return false;
return static_cast< bool >( proj_coordoperation_is_instantiable( context, coordinateOperation.get() ) );
}
#if 0
QStringList QgsProjUtils::nonAvailableGrids( const QString &projDef )
{
if ( projDef.isEmpty() )
return QStringList();
PJ_CONTEXT *context = QgsProjContext::get();
QgsProjUtils::proj_pj_unique_ptr op( proj_create( context, projDef.toUtf8().constData() ) ); < ---- - this always fails if grids are missing
if ( !op )
return QStringList();
QStringList res;
for ( int j = 0; j < proj_coordoperation_get_grid_used_count( context, op.get() ); ++j )
{
const char *shortName = nullptr;
int isAvailable = 0;
proj_coordoperation_get_grid_used( context, op.get(), j, &shortName, nullptr, nullptr, nullptr, nullptr, nullptr, &isAvailable );
if ( !isAvailable )
res << QString( shortName );
}
return res;
}
#endif
#endif

View File

@ -22,6 +22,7 @@
#include "qgis_core.h"
#include "qgsconfig.h"
#include <memory>
#include <QStringList>
#if !defined(USE_THREAD_LOCAL) || defined(Q_OS_WIN)
#include <QThreadStorage>
@ -74,7 +75,7 @@ class CORE_EXPORT QgsProjUtils
using proj_pj_unique_ptr = std::unique_ptr< PJ, ProjPJDeleter >;
/**
* Returns true if the given proj coordinate system uses angular units. \a projDef must be
* Returns TRUE if the given proj coordinate system uses angular units. \a projDef must be
* a proj string defining a CRS object.
*/
static bool usesAngularUnit( const QString &projDef );
@ -82,7 +83,7 @@ class CORE_EXPORT QgsProjUtils
//TODO - remove when proj 6.1 is minimum supported version, and replace with proj_normalize_for_visualization
/**
* Returns true if the given proj coordinate system uses requires y/x coordinate
* Returns TRUE if the given proj coordinate system uses requires y/x coordinate
* order instead of x/y.
*/
static bool axisOrderIsSwapped( const PJ *crs );
@ -93,6 +94,19 @@ class CORE_EXPORT QgsProjUtils
*/
static proj_pj_unique_ptr crsToSingleCrs( const PJ *crs );
/**
* Returns TRUE if a coordinate operation (specified via proj string) is available.
*/
static bool coordinateOperationIsAvailable( const QString &projDef );
#if 0 // not possible in current Proj 6 API
/**
* Given a coordinate operation (specified via proj string), returns a list of
* any required grids which are not currently available for use.
*/
static QStringList nonAvailableGrids( const QString &projDef );
#endif
#endif
#endif
};

View File

@ -1567,8 +1567,6 @@ QImage QgsRasterLayer::previewAsImage( QSize size, const QColor &bgColor, QImage
myRasterViewPort->mDrawnExtent = myExtent;
myRasterViewPort->mSrcCRS = QgsCoordinateReferenceSystem(); // will be invalid
myRasterViewPort->mDestCRS = QgsCoordinateReferenceSystem(); // will be invalid
myRasterViewPort->mSrcDatumTransform = -1;
myRasterViewPort->mDestDatumTransform = -1;
QgsMapToPixel *myMapToPixel = new QgsMapToPixel( myMapUnitsPerPixel );

View File

@ -161,16 +161,12 @@ QgsRasterLayerRenderer::QgsRasterLayerRenderer( QgsRasterLayer *layer, QgsRender
{
mRasterViewPort->mSrcCRS = layer->crs();
mRasterViewPort->mDestCRS = rendererContext.coordinateTransform().destinationCrs();
mRasterViewPort->mSrcDatumTransform = rendererContext.coordinateTransform().sourceDatumTransformId();
mRasterViewPort->mDestDatumTransform = rendererContext.coordinateTransform().destinationDatumTransformId();
mRasterViewPort->mTransformContext = rendererContext.transformContext();
}
else
{
mRasterViewPort->mSrcCRS = QgsCoordinateReferenceSystem(); // will be invalid
mRasterViewPort->mDestCRS = QgsCoordinateReferenceSystem(); // will be invalid
mRasterViewPort->mSrcDatumTransform = -1;
mRasterViewPort->mDestDatumTransform = -1;
}
// get dimensions of clipped raster image in device coordinate space (this is the size of the viewport)

View File

@ -67,9 +67,6 @@ struct CORE_EXPORT QgsRasterViewPort
//! \brief Target coordinate system
QgsCoordinateReferenceSystem mDestCRS;
int mSrcDatumTransform;
int mDestDatumTransform;
/**
* Coordinate transform context
*/

View File

@ -188,6 +188,35 @@ void TestQgsCoordinateTransform::isShortCircuited()
void TestQgsCoordinateTransform::contextShared()
{
//test implicit sharing of QgsCoordinateTransformContext
#if PROJ_VERSION_MAJOR >= 6
QgsCoordinateTransformContext original;
original.addCoordinateOperation( QgsCoordinateReferenceSystem( 3111 ), QgsCoordinateReferenceSystem( 3113 ), QStringLiteral( "proj" ) );
QgsCoordinateTransformContext copy( original );
QMap< QPair< QString, QString >, QString > expected;
expected.insert( qMakePair( QStringLiteral( "EPSG:3111" ), QStringLiteral( "EPSG:3113" ) ), QStringLiteral( "proj" ) );
QCOMPARE( original.coordinateOperations(), expected );
QCOMPARE( copy.coordinateOperations(), expected );
// trigger detach
copy.addCoordinateOperation( QgsCoordinateReferenceSystem( 3111 ), QgsCoordinateReferenceSystem( 3113 ), QStringLiteral( "proj2" ) );
QCOMPARE( original.coordinateOperations(), expected );
expected.insert( qMakePair( QStringLiteral( "EPSG:3111" ), QStringLiteral( "EPSG:3113" ) ), QStringLiteral( "proj2" ) );
QCOMPARE( copy.coordinateOperations(), expected );
// copy via assignment
QgsCoordinateTransformContext copy2;
copy2 = original;
expected.insert( qMakePair( QStringLiteral( "EPSG:3111" ), QStringLiteral( "EPSG:3113" ) ), QStringLiteral( "proj" ) );
QCOMPARE( original.coordinateOperations(), expected );
QCOMPARE( copy2.coordinateOperations(), expected );
copy2.addCoordinateOperation( QgsCoordinateReferenceSystem( 3111 ), QgsCoordinateReferenceSystem( 3113 ), QStringLiteral( "proj2" ) );
QCOMPARE( original.coordinateOperations(), expected );
expected.insert( qMakePair( QStringLiteral( "EPSG:3111" ), QStringLiteral( "EPSG:3113" ) ), QStringLiteral( "proj2" ) );
QCOMPARE( copy2.coordinateOperations(), expected );
#else
QgsCoordinateTransformContext original;
Q_NOWARN_DEPRECATED_PUSH
original.addSourceDestinationDatumTransform( QgsCoordinateReferenceSystem( 3111 ), QgsCoordinateReferenceSystem( 3113 ), 1, 2 );
@ -218,6 +247,7 @@ void TestQgsCoordinateTransform::contextShared()
QCOMPARE( copy2.sourceDestinationDatumTransforms(), expected );
Q_NOWARN_DEPRECATED_POP
#endif
}
void TestQgsCoordinateTransform::scaleFactor()
@ -228,7 +258,15 @@ void TestQgsCoordinateTransform::scaleFactor()
QFETCH( double, factor );
QgsCoordinateTransform ct( sourceCrs, destCrs, QgsProject::instance() );
QGSCOMPARENEAR( ct.scaleFactor( rect ), factor, 0.00000001 );
try
{
QGSCOMPARENEAR( ct.scaleFactor( rect ), factor, 0.000001 );
}
catch ( QgsCsException & )
{
QVERIFY( false );
}
}
void TestQgsCoordinateTransform::scaleFactor_data()
@ -244,10 +282,10 @@ void TestQgsCoordinateTransform::scaleFactor_data()
<< QgsRectangle( 2550000, 1200000, 2550100, 1200100 )
<< 1.1223316038381985e-5;
QTest::newRow( "Same map units" )
<< QgsCoordinateReferenceSystem::fromEpsgId( 2056 )
<< QgsCoordinateReferenceSystem::fromEpsgId( 21781 )
<< QgsRectangle( 2550000, 1200000, 2550100, 1200100 )
<< 1.0000000000248837;
<< QgsCoordinateReferenceSystem::fromEpsgId( 3111 )
<< QgsCoordinateReferenceSystem::fromEpsgId( 28355 )
<< QgsRectangle( 2560536.7, 2331787.5, 2653161.1, 2427370.4 )
<< 0.999632;
QTest::newRow( "Same CRS" )
<< QgsCoordinateReferenceSystem::fromEpsgId( 2056 )
<< QgsCoordinateReferenceSystem::fromEpsgId( 2056 )

View File

@ -17,7 +17,8 @@ from qgis.core import (QgsRectangle,
QgsCoordinateTransform,
QgsCoordinateTransformContext,
QgsDatumTransform,
QgsProject)
QgsProject,
QgsProjUtils)
from qgis.testing import start_app, unittest
start_app()
@ -71,68 +72,7 @@ class TestQgsCoordinateTransform(unittest.TestCase):
self.assertAlmostEquals(myTransformedExtentReverse.yMaximum(), myExtent.yMaximum())
self.assertAlmostEquals(myTransformedExtentReverse.yMinimum(), myExtent.yMinimum())
@unittest.skip('ifdefed out in c++ until required')
def testContextSingle(self):
"""
Various tests to ensure that datum transforms are correctly set respecting context
"""
context = QgsCoordinateTransformContext()
context.addSourceDatumTransform(QgsCoordinateReferenceSystem('EPSG:28356'), 1)
context.addDestinationDatumTransform(QgsCoordinateReferenceSystem('EPSG:4283'), 2)
context.addSourceDestinationDatumTransform(QgsCoordinateReferenceSystem('EPSG:28356'),
QgsCoordinateReferenceSystem('EPSG:4283'),
3, 4)
transform = QgsCoordinateTransform(QgsCoordinateReferenceSystem('EPSG:28354'), QgsCoordinateReferenceSystem('EPSG:28353'), context)
# should be no datum transforms
self.assertEqual(transform.sourceDatumTransformId(), -1)
self.assertEqual(transform.destinationDatumTransformId(), -1)
# matching source
transform = QgsCoordinateTransform(QgsCoordinateReferenceSystem('EPSG:28356'), QgsCoordinateReferenceSystem('EPSG:28353'), context)
self.assertEqual(transform.sourceDatumTransformId(), 1)
self.assertEqual(transform.destinationDatumTransformId(), -1)
# matching dest
transform = QgsCoordinateTransform(QgsCoordinateReferenceSystem('EPSG:28354'),
QgsCoordinateReferenceSystem('EPSG:4283'), context)
self.assertEqual(transform.sourceDatumTransformId(), -1)
self.assertEqual(transform.destinationDatumTransformId(), 2)
# matching src/dest pair
transform = QgsCoordinateTransform(QgsCoordinateReferenceSystem('EPSG:28356'),
QgsCoordinateReferenceSystem('EPSG:4283'), context)
self.assertEqual(transform.sourceDatumTransformId(), 3)
self.assertEqual(transform.destinationDatumTransformId(), 4)
# test manual overwriting
transform.setSourceDatumTransform(11)
transform.setDestinationDatumTransform(13)
self.assertEqual(transform.sourceDatumTransformId(), 11)
self.assertEqual(transform.destinationDatumTransformId(), 13)
# test that auto datum setting occurs when updating src/dest crs
transform.setSourceCrs(QgsCoordinateReferenceSystem('EPSG:28356'))
self.assertEqual(transform.sourceDatumTransformId(), 3)
self.assertEqual(transform.destinationDatumTransformId(), 4)
transform.setSourceDatumTransform(11)
transform.setDestinationDatumTransform(13)
transform.setDestinationCrs(QgsCoordinateReferenceSystem('EPSG:4283'))
self.assertEqual(transform.sourceDatumTransformId(), 3)
self.assertEqual(transform.destinationDatumTransformId(), 4)
transform.setSourceDatumTransform(11)
transform.setDestinationDatumTransform(13)
# delayed context set
transform = QgsCoordinateTransform()
self.assertEqual(transform.sourceDatumTransformId(), -1)
self.assertEqual(transform.destinationDatumTransformId(), -1)
transform.setSourceCrs(QgsCoordinateReferenceSystem('EPSG:28356'))
transform.setDestinationCrs(QgsCoordinateReferenceSystem('EPSG:4283'))
self.assertEqual(transform.sourceDatumTransformId(), -1)
self.assertEqual(transform.destinationDatumTransformId(), -1)
transform.setContext(context)
self.assertEqual(transform.sourceDatumTransformId(), 3)
self.assertEqual(transform.destinationDatumTransformId(), 4)
@unittest.skipIf(QgsProjUtils.projVersionMajor() >= 6, 'Skipped on proj6 builds')
def testContext(self):
"""
Various tests to ensure that datum transforms are correctly set respecting context
@ -194,6 +134,58 @@ class TestQgsCoordinateTransform(unittest.TestCase):
self.assertEqual(transform.destinationDatumTransformId(), 4)
self.assertEqual(list(transform.context().sourceDestinationDatumTransforms().keys()), [('EPSG:28356', 'EPSG:4283')])
@unittest.skipIf(QgsProjUtils.projVersionMajor() < 6, 'Skipped on non proj6 builds')
def testContextProj6(self):
"""
Various tests to ensure that datum transforms are correctly set respecting context
"""
context = QgsCoordinateTransformContext()
context.addCoordinateOperation(QgsCoordinateReferenceSystem('EPSG:28356'),
QgsCoordinateReferenceSystem('EPSG:4283'),
'proj')
transform = QgsCoordinateTransform(QgsCoordinateReferenceSystem('EPSG:28354'), QgsCoordinateReferenceSystem('EPSG:28353'), context)
self.assertEqual(list(transform.context().coordinateOperations().keys()), [('EPSG:28356', 'EPSG:4283')])
# should be no coordinate operation
self.assertEqual(transform.coordinateOperation(), '')
# matching source
transform = QgsCoordinateTransform(QgsCoordinateReferenceSystem('EPSG:28356'), QgsCoordinateReferenceSystem('EPSG:28353'), context)
self.assertEqual(transform.coordinateOperation(), '')
# matching dest
transform = QgsCoordinateTransform(QgsCoordinateReferenceSystem('EPSG:28354'),
QgsCoordinateReferenceSystem('EPSG:4283'), context)
self.assertEqual(transform.coordinateOperation(), '')
# matching src/dest pair
transform = QgsCoordinateTransform(QgsCoordinateReferenceSystem('EPSG:28356'),
QgsCoordinateReferenceSystem('EPSG:4283'), context)
self.assertEqual(transform.coordinateOperation(), 'proj')
# test manual overwriting
transform.setCoordinateOperation('proj2')
self.assertEqual(transform.coordinateOperation(), 'proj2')
# test that auto operation setting occurs when updating src/dest crs
transform.setSourceCrs(QgsCoordinateReferenceSystem('EPSG:28356'))
self.assertEqual(transform.coordinateOperation(), 'proj')
transform.setCoordinateOperation('proj2')
transform.setDestinationCrs(QgsCoordinateReferenceSystem('EPSG:4283'))
self.assertEqual(transform.coordinateOperation(), 'proj')
transform.setCoordinateOperation('proj2')
# delayed context set
transform = QgsCoordinateTransform()
self.assertEqual(transform.coordinateOperation(), '')
transform.setSourceCrs(QgsCoordinateReferenceSystem('EPSG:28356'))
transform.setDestinationCrs(QgsCoordinateReferenceSystem('EPSG:4283'))
self.assertEqual(transform.coordinateOperation(), '')
transform.setContext(context)
self.assertEqual(transform.coordinateOperation(), 'proj')
self.assertEqual(list(transform.context().coordinateOperations().keys()), [('EPSG:28356', 'EPSG:4283')])
@unittest.skipIf(QgsProjUtils.projVersionMajor() >= 6, 'Skipped on proj6 builds')
def testProjectContext(self):
"""
Test creating transform using convenience constructor which takes project reference
@ -208,6 +200,21 @@ class TestQgsCoordinateTransform(unittest.TestCase):
self.assertEqual(transform.sourceDatumTransformId(), 1)
self.assertEqual(transform.destinationDatumTransformId(), 2)
@unittest.skipIf(QgsProjUtils.projVersionMajor() < 6, 'Skipped on non proj6 builds')
def testProjectContextProj6(self):
"""
Test creating transform using convenience constructor which takes project reference
"""
p = QgsProject()
context = p.transformContext()
context.addCoordinateOperation(QgsCoordinateReferenceSystem('EPSG:28356'),
QgsCoordinateReferenceSystem('EPSG:3111'), 'proj')
p.setTransformContext(context)
transform = QgsCoordinateTransform(QgsCoordinateReferenceSystem('EPSG:28356'), QgsCoordinateReferenceSystem('EPSG:3111'), p)
self.assertEqual(transform.coordinateOperation(), 'proj')
@unittest.skipIf(QgsProjUtils.projVersionMajor() >= 6, 'Skipped on proj6 builds')
def testTransformInfo(self):
# hopefully this transform is available on all platforms!
transforms = QgsDatumTransform.datumTransformations(QgsCoordinateReferenceSystem(4613), QgsCoordinateReferenceSystem(4326))
@ -230,6 +237,7 @@ class TestQgsCoordinateTransform(unittest.TestCase):
self.assertIn('EPSG:4613', [QgsDatumTransform.datumTransformInfo(t.destinationTransformId).sourceCrsAuthId for t in
transforms])
@unittest.skipIf(QgsProjUtils.projVersionMajor() >= 6, 'Skipped on proj6 builds')
def testStringToTransformId(self):
"""
Test converting proj strings to corresponding datum IDs

View File

@ -17,7 +17,8 @@ from qgis.core import (QgsCoordinateReferenceSystem,
QgsDatumTransform,
QgsReadWriteContext,
QgsProject,
QgsSettings)
QgsSettings,
QgsProjUtils)
from qgis.testing import start_app, unittest
from qgis.PyQt.QtXml import QDomDocument
from qgis.PyQt.QtTest import QSignalSpy
@ -32,14 +33,16 @@ class TestQgsCoordinateTransformContext(unittest.TestCase):
def setUpClass(cls):
"""Run before all tests"""
QCoreApplication.setOrganizationName("QGIS_Test")
QCoreApplication.setOrganizationDomain("TestPyQgsWFSProvider.com")
QCoreApplication.setApplicationName("TestPyQgsWFSProvider")
QCoreApplication.setOrganizationDomain("TestQgsCoordinateTransformContext.com")
QCoreApplication.setApplicationName("TestQgsCoordinateTransformContext")
QgsSettings().clear()
@unittest.skipIf(QgsProjUtils.projVersionMajor() >= 6, 'Skipped on proj6 builds')
def testSourceDestinationDatumTransforms(self):
context = QgsCoordinateTransformContext()
self.assertEqual(context.sourceDestinationDatumTransforms(), {})
self.assertFalse(context.hasTransform(QgsCoordinateReferenceSystem('EPSG:3111'), QgsCoordinateReferenceSystem('EPSG:4283')))
self.assertFalse(
context.hasTransform(QgsCoordinateReferenceSystem('EPSG:3111'), QgsCoordinateReferenceSystem('EPSG:4283')))
self.assertTrue(context.addSourceDestinationDatumTransform(QgsCoordinateReferenceSystem('EPSG:3111'),
QgsCoordinateReferenceSystem('EPSG:4283'), 1, 2))
self.assertTrue(
@ -48,92 +51,180 @@ class TestQgsCoordinateTransformContext(unittest.TestCase):
context.hasTransform(QgsCoordinateReferenceSystem('EPSG:3111'), QgsCoordinateReferenceSystem('EPSG:4326')))
self.assertFalse(
context.hasTransform(QgsCoordinateReferenceSystem('EPSG:3113'), QgsCoordinateReferenceSystem('EPSG:4283')))
self.assertEqual(context.sourceDestinationDatumTransforms(), {('EPSG:3111', 'EPSG:4283'): QgsDatumTransform.TransformPair(1, 2)})
self.assertEqual(context.sourceDestinationDatumTransforms(),
{('EPSG:3111', 'EPSG:4283'): QgsDatumTransform.TransformPair(1, 2)})
self.assertTrue(context.addSourceDestinationDatumTransform(QgsCoordinateReferenceSystem('EPSG:28356'),
QgsCoordinateReferenceSystem(4283), 3, 4))
self.assertEqual(context.sourceDestinationDatumTransforms(), {('EPSG:3111', 'EPSG:4283'): QgsDatumTransform.TransformPair(1, 2),
('EPSG:28356', 'EPSG:4283'): QgsDatumTransform.TransformPair(3, 4)})
self.assertEqual(context.sourceDestinationDatumTransforms(),
{('EPSG:3111', 'EPSG:4283'): QgsDatumTransform.TransformPair(1, 2),
('EPSG:28356', 'EPSG:4283'): QgsDatumTransform.TransformPair(3, 4)})
self.assertTrue(context.addSourceDestinationDatumTransform(QgsCoordinateReferenceSystem('EPSG:28356'),
QgsCoordinateReferenceSystem(28357), 7, 8))
self.assertEqual(context.sourceDestinationDatumTransforms(), {('EPSG:3111', 'EPSG:4283'): QgsDatumTransform.TransformPair(1, 2),
('EPSG:28356', 'EPSG:4283'): QgsDatumTransform.TransformPair(3, 4),
('EPSG:28356', 'EPSG:28357'): QgsDatumTransform.TransformPair(7, 8)})
self.assertEqual(context.sourceDestinationDatumTransforms(),
{('EPSG:3111', 'EPSG:4283'): QgsDatumTransform.TransformPair(1, 2),
('EPSG:28356', 'EPSG:4283'): QgsDatumTransform.TransformPair(3, 4),
('EPSG:28356', 'EPSG:28357'): QgsDatumTransform.TransformPair(7, 8)})
self.assertTrue(context.addSourceDestinationDatumTransform(QgsCoordinateReferenceSystem('EPSG:28356'),
QgsCoordinateReferenceSystem('EPSG:28357'), 9, 11))
self.assertEqual(context.sourceDestinationDatumTransforms(), {('EPSG:3111', 'EPSG:4283'): QgsDatumTransform.TransformPair(1, 2),
('EPSG:28356', 'EPSG:4283'): QgsDatumTransform.TransformPair(3, 4),
('EPSG:28356', 'EPSG:28357'): QgsDatumTransform.TransformPair(9, 11)})
self.assertEqual(context.sourceDestinationDatumTransforms(),
{('EPSG:3111', 'EPSG:4283'): QgsDatumTransform.TransformPair(1, 2),
('EPSG:28356', 'EPSG:4283'): QgsDatumTransform.TransformPair(3, 4),
('EPSG:28356', 'EPSG:28357'): QgsDatumTransform.TransformPair(9, 11)})
# invalid additions
self.assertFalse(context.addSourceDestinationDatumTransform(QgsCoordinateReferenceSystem(),
QgsCoordinateReferenceSystem('EPSG:28357'), 9, 11))
self.assertEqual(context.sourceDestinationDatumTransforms(), {('EPSG:3111', 'EPSG:4283'): QgsDatumTransform.TransformPair(1, 2),
('EPSG:28356', 'EPSG:4283'): QgsDatumTransform.TransformPair(3, 4),
('EPSG:28356', 'EPSG:28357'): QgsDatumTransform.TransformPair(9, 11)})
self.assertEqual(context.sourceDestinationDatumTransforms(),
{('EPSG:3111', 'EPSG:4283'): QgsDatumTransform.TransformPair(1, 2),
('EPSG:28356', 'EPSG:4283'): QgsDatumTransform.TransformPair(3, 4),
('EPSG:28356', 'EPSG:28357'): QgsDatumTransform.TransformPair(9, 11)})
self.assertFalse(context.addSourceDestinationDatumTransform(QgsCoordinateReferenceSystem('EPSG:3111'),
QgsCoordinateReferenceSystem(), 9, 11))
self.assertEqual(context.sourceDestinationDatumTransforms(), {('EPSG:3111', 'EPSG:4283'): QgsDatumTransform.TransformPair(1, 2),
('EPSG:28356', 'EPSG:4283'): QgsDatumTransform.TransformPair(3, 4),
('EPSG:28356', 'EPSG:28357'): QgsDatumTransform.TransformPair(9, 11)})
self.assertEqual(context.sourceDestinationDatumTransforms(),
{('EPSG:3111', 'EPSG:4283'): QgsDatumTransform.TransformPair(1, 2),
('EPSG:28356', 'EPSG:4283'): QgsDatumTransform.TransformPair(3, 4),
('EPSG:28356', 'EPSG:28357'): QgsDatumTransform.TransformPair(9, 11)})
# indicate no transform required
self.assertTrue(context.addSourceDestinationDatumTransform(QgsCoordinateReferenceSystem(28357),
QgsCoordinateReferenceSystem(28356), -1, -1))
self.assertEqual(context.sourceDestinationDatumTransforms(), {('EPSG:3111', 'EPSG:4283'): QgsDatumTransform.TransformPair(1, 2),
('EPSG:28356', 'EPSG:4283'): QgsDatumTransform.TransformPair(3, 4),
('EPSG:28356', 'EPSG:28357'): QgsDatumTransform.TransformPair(9, 11),
('EPSG:28357', 'EPSG:28356'): QgsDatumTransform.TransformPair(-1, -1)})
self.assertEqual(context.sourceDestinationDatumTransforms(),
{('EPSG:3111', 'EPSG:4283'): QgsDatumTransform.TransformPair(1, 2),
('EPSG:28356', 'EPSG:4283'): QgsDatumTransform.TransformPair(3, 4),
('EPSG:28356', 'EPSG:28357'): QgsDatumTransform.TransformPair(9, 11),
('EPSG:28357', 'EPSG:28356'): QgsDatumTransform.TransformPair(-1, -1)})
self.assertTrue(context.addSourceDestinationDatumTransform(QgsCoordinateReferenceSystem(3111),
QgsCoordinateReferenceSystem(28356), 17, -1))
self.assertEqual(context.sourceDestinationDatumTransforms(), {('EPSG:3111', 'EPSG:4283'): QgsDatumTransform.TransformPair(1, 2),
('EPSG:28356', 'EPSG:4283'): QgsDatumTransform.TransformPair(3, 4),
('EPSG:28356', 'EPSG:28357'): QgsDatumTransform.TransformPair(9, 11),
('EPSG:28357', 'EPSG:28356'): QgsDatumTransform.TransformPair(-1, -1),
('EPSG:3111', 'EPSG:28356'): QgsDatumTransform.TransformPair(17, -1)})
self.assertEqual(context.sourceDestinationDatumTransforms(),
{('EPSG:3111', 'EPSG:4283'): QgsDatumTransform.TransformPair(1, 2),
('EPSG:28356', 'EPSG:4283'): QgsDatumTransform.TransformPair(3, 4),
('EPSG:28356', 'EPSG:28357'): QgsDatumTransform.TransformPair(9, 11),
('EPSG:28357', 'EPSG:28356'): QgsDatumTransform.TransformPair(-1, -1),
('EPSG:3111', 'EPSG:28356'): QgsDatumTransform.TransformPair(17, -1)})
self.assertTrue(context.addSourceDestinationDatumTransform(QgsCoordinateReferenceSystem(3113),
QgsCoordinateReferenceSystem(28356), -1, 18))
self.assertEqual(context.sourceDestinationDatumTransforms(), {('EPSG:3111', 'EPSG:4283'): QgsDatumTransform.TransformPair(1, 2),
('EPSG:28356', 'EPSG:4283'): QgsDatumTransform.TransformPair(3, 4),
('EPSG:28356', 'EPSG:28357'): QgsDatumTransform.TransformPair(9, 11),
('EPSG:28357', 'EPSG:28356'): QgsDatumTransform.TransformPair(-1, -1),
('EPSG:3111', 'EPSG:28356'): QgsDatumTransform.TransformPair(17, -1),
('EPSG:3113', 'EPSG:28356'): QgsDatumTransform.TransformPair(-1, 18)})
self.assertEqual(context.sourceDestinationDatumTransforms(),
{('EPSG:3111', 'EPSG:4283'): QgsDatumTransform.TransformPair(1, 2),
('EPSG:28356', 'EPSG:4283'): QgsDatumTransform.TransformPair(3, 4),
('EPSG:28356', 'EPSG:28357'): QgsDatumTransform.TransformPair(9, 11),
('EPSG:28357', 'EPSG:28356'): QgsDatumTransform.TransformPair(-1, -1),
('EPSG:3111', 'EPSG:28356'): QgsDatumTransform.TransformPair(17, -1),
('EPSG:3113', 'EPSG:28356'): QgsDatumTransform.TransformPair(-1, 18)})
# remove non-existing
context.removeCoordinateOperation(QgsCoordinateReferenceSystem(3113), QgsCoordinateReferenceSystem(3111))
self.assertEqual(context.sourceDestinationDatumTransforms(), {('EPSG:3111', 'EPSG:4283'): QgsDatumTransform.TransformPair(1, 2),
('EPSG:28356', 'EPSG:4283'): QgsDatumTransform.TransformPair(3, 4),
('EPSG:28356', 'EPSG:28357'): QgsDatumTransform.TransformPair(9, 11),
('EPSG:28357', 'EPSG:28356'): QgsDatumTransform.TransformPair(-1, -1),
('EPSG:3111', 'EPSG:28356'): QgsDatumTransform.TransformPair(17, -1),
('EPSG:3113', 'EPSG:28356'): QgsDatumTransform.TransformPair(-1, 18)})
self.assertEqual(context.sourceDestinationDatumTransforms(),
{('EPSG:3111', 'EPSG:4283'): QgsDatumTransform.TransformPair(1, 2),
('EPSG:28356', 'EPSG:4283'): QgsDatumTransform.TransformPair(3, 4),
('EPSG:28356', 'EPSG:28357'): QgsDatumTransform.TransformPair(9, 11),
('EPSG:28357', 'EPSG:28356'): QgsDatumTransform.TransformPair(-1, -1),
('EPSG:3111', 'EPSG:28356'): QgsDatumTransform.TransformPair(17, -1),
('EPSG:3113', 'EPSG:28356'): QgsDatumTransform.TransformPair(-1, 18)})
# remove existing
context.removeCoordinateOperation(QgsCoordinateReferenceSystem(3111),
QgsCoordinateReferenceSystem(4283))
self.assertEqual(context.sourceDestinationDatumTransforms(), {('EPSG:28356', 'EPSG:4283'): QgsDatumTransform.TransformPair(3, 4),
('EPSG:28356', 'EPSG:28357'): QgsDatumTransform.TransformPair(9, 11),
('EPSG:28357', 'EPSG:28356'): QgsDatumTransform.TransformPair(-1, -1),
('EPSG:3111', 'EPSG:28356'): QgsDatumTransform.TransformPair(17, -1),
('EPSG:3113', 'EPSG:28356'): QgsDatumTransform.TransformPair(-1, 18)})
self.assertEqual(context.sourceDestinationDatumTransforms(),
{('EPSG:28356', 'EPSG:4283'): QgsDatumTransform.TransformPair(3, 4),
('EPSG:28356', 'EPSG:28357'): QgsDatumTransform.TransformPair(9, 11),
('EPSG:28357', 'EPSG:28356'): QgsDatumTransform.TransformPair(-1, -1),
('EPSG:3111', 'EPSG:28356'): QgsDatumTransform.TransformPair(17, -1),
('EPSG:3113', 'EPSG:28356'): QgsDatumTransform.TransformPair(-1, 18)})
context.removeCoordinateOperation(QgsCoordinateReferenceSystem(3111),
QgsCoordinateReferenceSystem(28356))
self.assertEqual(context.sourceDestinationDatumTransforms(), {('EPSG:28356', 'EPSG:4283'): QgsDatumTransform.TransformPair(3, 4),
('EPSG:28356', 'EPSG:28357'): QgsDatumTransform.TransformPair(9, 11),
('EPSG:28357', 'EPSG:28356'): QgsDatumTransform.TransformPair(-1, -1),
('EPSG:3113', 'EPSG:28356'): QgsDatumTransform.TransformPair(-1, 18)})
self.assertEqual(context.sourceDestinationDatumTransforms(),
{('EPSG:28356', 'EPSG:4283'): QgsDatumTransform.TransformPair(3, 4),
('EPSG:28356', 'EPSG:28357'): QgsDatumTransform.TransformPair(9, 11),
('EPSG:28357', 'EPSG:28356'): QgsDatumTransform.TransformPair(-1, -1),
('EPSG:3113', 'EPSG:28356'): QgsDatumTransform.TransformPair(-1, 18)})
context.clear()
self.assertEqual(context.sourceDestinationDatumTransforms(), {})
@unittest.skipIf(QgsProjUtils.projVersionMajor() < 6, 'Skipped on non proj6 builds')
def testSourceDestinationDatumTransformsProj6(self):
context = QgsCoordinateTransformContext()
self.assertEqual(context.sourceDestinationDatumTransforms(), {})
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.assertTrue(context.addCoordinateOperation(QgsCoordinateReferenceSystem('EPSG:3111'),
QgsCoordinateReferenceSystem('EPSG:4283'), proj_string))
self.assertTrue(
context.hasTransform(QgsCoordinateReferenceSystem('EPSG:3111'), QgsCoordinateReferenceSystem('EPSG:4283')))
self.assertFalse(
context.hasTransform(QgsCoordinateReferenceSystem('EPSG:3111'), QgsCoordinateReferenceSystem('EPSG:4326')))
self.assertFalse(
context.hasTransform(QgsCoordinateReferenceSystem('EPSG:3113'), QgsCoordinateReferenceSystem('EPSG:4283')))
self.assertEqual(context.coordinateOperations(), {('EPSG:3111', 'EPSG:4283'): proj_string})
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))
self.assertEqual(context.coordinateOperations(), {('EPSG:3111', 'EPSG:4283'): proj_string,
('EPSG:28356', 'EPSG:4283'): proj_string_2})
proj_string_3 = '+proj=pipeline +step +inv +proj=utm +zone=56 +south +ellps=GRS80 +step +proj=utm +zone=57 +south +ellps=GRS80'
self.assertTrue(context.addCoordinateOperation(QgsCoordinateReferenceSystem('EPSG:28356'),
QgsCoordinateReferenceSystem(28357), proj_string_3))
self.assertEqual(context.coordinateOperations(), {('EPSG:3111', 'EPSG:4283'): proj_string,
('EPSG:28356', 'EPSG:4283'): proj_string_2,
('EPSG:28356', 'EPSG:28357'): proj_string_3})
self.assertTrue(context.addCoordinateOperation(QgsCoordinateReferenceSystem('EPSG:28356'),
QgsCoordinateReferenceSystem('EPSG:28357'),
'some other proj string'))
self.assertEqual(context.coordinateOperations(), {('EPSG:3111', 'EPSG:4283'): proj_string,
('EPSG:28356', 'EPSG:4283'): proj_string_2,
('EPSG:28356', 'EPSG:28357'): 'some other proj string'})
# invalid additions
self.assertFalse(context.addCoordinateOperation(QgsCoordinateReferenceSystem(),
QgsCoordinateReferenceSystem('EPSG:28357'), 'bad proj'))
self.assertEqual(context.coordinateOperations(), {('EPSG:3111', 'EPSG:4283'): proj_string,
('EPSG:28356', 'EPSG:4283'): proj_string_2,
('EPSG:28356', 'EPSG:28357'): 'some other proj string'})
self.assertFalse(context.addCoordinateOperation(QgsCoordinateReferenceSystem('EPSG:3111'),
QgsCoordinateReferenceSystem(), 'bad proj'))
self.assertEqual(context.coordinateOperations(), {('EPSG:3111', 'EPSG:4283'): proj_string,
('EPSG:28356', 'EPSG:4283'): proj_string_2,
('EPSG:28356', 'EPSG:28357'): 'some other proj string'})
# indicate no transform required
self.assertTrue(context.addCoordinateOperation(QgsCoordinateReferenceSystem(28357),
QgsCoordinateReferenceSystem(28356), ''))
self.assertEqual(context.coordinateOperations(), {('EPSG:3111', 'EPSG:4283'): proj_string,
('EPSG:28356', 'EPSG:4283'): proj_string_2,
('EPSG:28356', 'EPSG:28357'): 'some other proj string',
('EPSG:28357', 'EPSG:28356'): ''})
# remove non-existing
context.removeCoordinateOperation(QgsCoordinateReferenceSystem(3113), QgsCoordinateReferenceSystem(3111))
self.assertEqual(context.coordinateOperations(), {('EPSG:3111', 'EPSG:4283'): proj_string,
('EPSG:28356', 'EPSG:4283'): proj_string_2,
('EPSG:28356', 'EPSG:28357'): 'some other proj string',
('EPSG:28357', 'EPSG:28356'): ''})
# remove existing
context.removeCoordinateOperation(QgsCoordinateReferenceSystem(3111),
QgsCoordinateReferenceSystem(4283))
self.assertEqual(context.coordinateOperations(), {('EPSG:28356', 'EPSG:4283'): proj_string_2,
('EPSG:28356', 'EPSG:28357'): 'some other proj string',
('EPSG:28357', 'EPSG:28356'): ''})
context.removeCoordinateOperation(QgsCoordinateReferenceSystem(28356),
QgsCoordinateReferenceSystem(28357))
self.assertEqual(context.coordinateOperations(), {('EPSG:28356', 'EPSG:4283'): proj_string_2,
('EPSG:28357', 'EPSG:28356'): ''})
context.clear()
self.assertEqual(context.coordinateOperations(), {})
@unittest.skipIf(QgsProjUtils.projVersionMajor() >= 6, 'Skipped on proj6 builds')
def testCalculateSourceDest(self):
context = QgsCoordinateTransformContext()
#empty context
# empty context
self.assertEqual(context.calculateDatumTransforms(QgsCoordinateReferenceSystem('EPSG:3111'),
QgsCoordinateReferenceSystem('EPSG:4283')),
QgsDatumTransform.TransformPair(-1, -1))
#add specific source/dest pair - should take precedence
# add specific source/dest pair - should take precedence
context.addSourceDestinationDatumTransform(QgsCoordinateReferenceSystem('EPSG:28356'),
QgsCoordinateReferenceSystem('EPSG:4283'),
3, 4)
@ -151,6 +242,34 @@ class TestQgsCoordinateTransformContext(unittest.TestCase):
QgsCoordinateReferenceSystem('EPSG:28356')),
QgsDatumTransform.TransformPair(4, 3))
@unittest.skipIf(QgsProjUtils.projVersionMajor() < 6, 'Skipped on non proj6 builds')
def testCalculateSourceDestProj6(self):
context = QgsCoordinateTransformContext()
# empty context
self.assertEqual(context.calculateCoordinateOperation(QgsCoordinateReferenceSystem('EPSG:3111'),
QgsCoordinateReferenceSystem('EPSG:4283')),
'')
# add specific source/dest pair - should take precedence
context.addCoordinateOperation(QgsCoordinateReferenceSystem('EPSG:28356'),
QgsCoordinateReferenceSystem('EPSG:4283'),
'proj 1')
self.assertEqual(context.calculateCoordinateOperation(QgsCoordinateReferenceSystem('EPSG:28356'),
QgsCoordinateReferenceSystem('EPSG:4283')),
'proj 1')
self.assertEqual(context.calculateCoordinateOperation(QgsCoordinateReferenceSystem('EPSG:3111'),
QgsCoordinateReferenceSystem('EPSG:4283')),
'')
self.assertEqual(context.calculateCoordinateOperation(QgsCoordinateReferenceSystem('EPSG:28356'),
QgsCoordinateReferenceSystem('EPSG:3111')),
'')
# check that reverse transforms are automatically supported
self.assertEqual(context.calculateCoordinateOperation(QgsCoordinateReferenceSystem('EPSG:4283'),
QgsCoordinateReferenceSystem('EPSG:28356')),
'proj 1')
@unittest.skipIf(QgsProjUtils.projVersionMajor() >= 6, 'Skipped on proj6 builds')
def testWriteReadXml(self):
# setup a context
context = QgsCoordinateTransformContext()
@ -166,12 +285,15 @@ class TestQgsCoordinateTransformContext(unittest.TestCase):
QgsCoordinateReferenceSystem(4326))[0].destinationTransformId
self.assertTrue(context.addSourceDestinationDatumTransform(QgsCoordinateReferenceSystem(4204),
QgsCoordinateReferenceSystem(4326), source_id_1, dest_id_1))
QgsCoordinateReferenceSystem(4326), source_id_1,
dest_id_1))
self.assertTrue(context.addSourceDestinationDatumTransform(QgsCoordinateReferenceSystem(4205),
QgsCoordinateReferenceSystem(4326), source_id_2, dest_id_2))
QgsCoordinateReferenceSystem(4326), source_id_2,
dest_id_2))
self.assertEqual(context.sourceDestinationDatumTransforms(), {('EPSG:4204', 'EPSG:4326'): QgsDatumTransform.TransformPair(source_id_1, dest_id_1),
('EPSG:4205', 'EPSG:4326'): QgsDatumTransform.TransformPair(source_id_2, dest_id_2)})
self.assertEqual(context.sourceDestinationDatumTransforms(),
{('EPSG:4204', 'EPSG:4326'): QgsDatumTransform.TransformPair(source_id_1, dest_id_1),
('EPSG:4205', 'EPSG:4326'): QgsDatumTransform.TransformPair(source_id_2, dest_id_2)})
# save to xml
doc = QDomDocument("testdoc")
@ -183,9 +305,42 @@ class TestQgsCoordinateTransformContext(unittest.TestCase):
context2.readXml(elem, QgsReadWriteContext())
# check result
self.assertEqual(context2.sourceDestinationDatumTransforms(), {('EPSG:4204', 'EPSG:4326'): QgsDatumTransform.TransformPair(source_id_1, dest_id_1),
('EPSG:4205', 'EPSG:4326'): QgsDatumTransform.TransformPair(source_id_2, dest_id_2)})
self.assertEqual(context2.sourceDestinationDatumTransforms(),
{('EPSG:4204', 'EPSG:4326'): QgsDatumTransform.TransformPair(source_id_1, dest_id_1),
('EPSG:4205', 'EPSG:4326'): QgsDatumTransform.TransformPair(source_id_2, dest_id_2)})
@unittest.skipIf(QgsProjUtils.projVersionMajor() < 6, 'Skipped on non proj6 builds')
def testWriteReadXmlProj6(self):
# setup a context
context = QgsCoordinateTransformContext()
proj_1 = '+proj=pipeline +step +proj=axisswap +order=2,1 +step +proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=push +v_3 +step +proj=cart +ellps=intl +step +proj=helmert +x=-18.944 +y=-379.364 +z=-24.063 +rx=-0.04 +ry=0.764 +rz=-6.431 +s=3.657 +convention=coordinate_frame +step +inv +proj=cart +ellps=WGS84 +step +proj=pop +v_3 +step +proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1'
proj_2 = '+proj=pipeline +step +proj=axisswap +order=2,1 +step +proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=push +v_3 +step +proj=cart +ellps=intl +step +proj=helmert +x=-150 +y=-250 +z=-1 +step +inv +proj=cart +ellps=WGS84 +step +proj=pop +v_3 +step +proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1'
self.assertTrue(context.addCoordinateOperation(QgsCoordinateReferenceSystem(4204),
QgsCoordinateReferenceSystem(4326), proj_1))
self.assertTrue(context.addCoordinateOperation(QgsCoordinateReferenceSystem(4205),
QgsCoordinateReferenceSystem(4326), proj_2))
self.assertEqual(context.coordinateOperations(),
{('EPSG:4204', 'EPSG:4326'): proj_1,
('EPSG:4205', 'EPSG:4326'): proj_2})
# save to xml
doc = QDomDocument("testdoc")
elem = doc.createElement("test")
context.writeXml(elem, QgsReadWriteContext())
# restore from xml
context2 = QgsCoordinateTransformContext()
context2.readXml(elem, QgsReadWriteContext())
# check result
self.assertEqual(context2.coordinateOperations(),
{('EPSG:4204', 'EPSG:4326'): proj_1,
('EPSG:4205', 'EPSG:4326'): proj_2})
@unittest.skipIf(QgsProjUtils.projVersionMajor() >= 6, 'Skipped on proj6 builds')
def testMissingTransforms(self):
# fudge context xml with a missing transform
doc = QDomDocument("testdoc")
@ -209,6 +364,34 @@ class TestQgsCoordinateTransformContext(unittest.TestCase):
# check result
self.assertEqual(errors, ['not valid', 'not valid 2'])
@unittest.skipIf(QgsProjUtils.projVersionMajor() < 6, 'Skipped on non proj6 builds')
def testMissingTransformsProj6(self):
return # TODO -- this seems impossible to determine with existing PROJ6 api
# fudge context xml with a missing transform
doc = QDomDocument("testdoc")
elem = doc.createElement("test")
contextElem = doc.createElement("transformContext")
transformElem = doc.createElement("srcDest")
transformElem.setAttribute("source", 'EPSG:4204')
transformElem.setAttribute("dest", 'EPSG:4326')
# fake a proj string with a grid which will NEVER exist
fake_proj = '+proj=pipeline +step +proj=axisswap +order=2,1 +step +proj=unitconvert +xy_in=deg +xy_out=rad +step +inv +proj=hgridshift +grids=this_is_not_a_real_grid.gsb +step +proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1'
transformElem.setAttribute("coordinateOp", fake_proj)
contextElem.appendChild(transformElem)
elem2 = doc.createElement("test2")
elem2.appendChild(contextElem)
# restore from xml
context2 = QgsCoordinateTransformContext()
ok, errors = context2.readXml(elem2, QgsReadWriteContext())
self.assertFalse(ok)
# check result
self.assertEqual(errors, ['not valid'])
@unittest.skipIf(QgsProjUtils.projVersionMajor() >= 6, 'Skipped on proj6 builds')
def testProject(self):
"""
Test project's transform context
@ -220,8 +403,25 @@ class TestQgsCoordinateTransformContext(unittest.TestCase):
QgsCoordinateReferenceSystem('EPSG:4283'), 1, 2)
project.setTransformContext(context)
self.assertEqual(len(context_changed_spy), 1)
self.assertEqual(project.transformContext().sourceDestinationDatumTransforms(), {('EPSG:3111', 'EPSG:4283'): QgsDatumTransform.TransformPair(1, 2)})
self.assertEqual(project.transformContext().sourceDestinationDatumTransforms(),
{('EPSG:3111', 'EPSG:4283'): QgsDatumTransform.TransformPair(1, 2)})
@unittest.skipIf(QgsProjUtils.projVersionMajor() < 6, 'Skipped on non proj6 builds')
def testProjectProj6(self):
"""
Test project's transform context
"""
project = QgsProject()
context_changed_spy = QSignalSpy(project.transformContextChanged)
context = project.transformContext()
context.addCoordinateOperation(QgsCoordinateReferenceSystem('EPSG:3111'),
QgsCoordinateReferenceSystem('EPSG:4283'), 'proj')
project.setTransformContext(context)
self.assertEqual(len(context_changed_spy), 1)
self.assertEqual(project.transformContext().coordinateOperations(),
{('EPSG:3111', 'EPSG:4283'): 'proj'})
@unittest.skipIf(QgsProjUtils.projVersionMajor() >= 6, 'Skipped on proj6 builds')
def testReadWriteSettings(self):
context = QgsCoordinateTransformContext()
context.readSettings()
@ -240,9 +440,11 @@ class TestQgsCoordinateTransformContext(unittest.TestCase):
self.assertEqual(context.sourceDestinationDatumTransforms(), {})
self.assertTrue(context.addSourceDestinationDatumTransform(QgsCoordinateReferenceSystem('EPSG:4204'),
QgsCoordinateReferenceSystem('EPSG:4326'), source_id_1, dest_id_1))
QgsCoordinateReferenceSystem('EPSG:4326'),
source_id_1, dest_id_1))
self.assertTrue(context.addSourceDestinationDatumTransform(QgsCoordinateReferenceSystem('EPSG:4205'),
QgsCoordinateReferenceSystem(4326), source_id_2, dest_id_2))
QgsCoordinateReferenceSystem(4326), source_id_2,
dest_id_2))
self.assertEqual(context.sourceDestinationDatumTransforms(),
{('EPSG:4204', 'EPSG:4326'): QgsDatumTransform.TransformPair(source_id_1, dest_id_1),
@ -261,6 +463,40 @@ class TestQgsCoordinateTransformContext(unittest.TestCase):
{('EPSG:4204', 'EPSG:4326'): QgsDatumTransform.TransformPair(source_id_1, dest_id_1),
('EPSG:4205', 'EPSG:4326'): QgsDatumTransform.TransformPair(source_id_2, dest_id_2)})
@unittest.skipIf(QgsProjUtils.projVersionMajor() < 6, 'Skipped on non proj6 builds')
def testReadWriteSettingsProj6(self):
context = QgsCoordinateTransformContext()
context.readSettings()
proj_1 = '+proj=pipeline +step +proj=axisswap +order=2,1 +step +proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=push +v_3 +step +proj=cart +ellps=intl +step +proj=helmert +x=-18.944 +y=-379.364 +z=-24.063 +rx=-0.04 +ry=0.764 +rz=-6.431 +s=3.657 +convention=coordinate_frame +step +inv +proj=cart +ellps=WGS84 +step +proj=pop +v_3 +step +proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1'
proj_2 = '+proj=pipeline +step +proj=axisswap +order=2,1 +step +proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=push +v_3 +step +proj=cart +ellps=intl +step +proj=helmert +x=-150 +y=-250 +z=-1 +step +inv +proj=cart +ellps=WGS84 +step +proj=pop +v_3 +step +proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1'
# should be empty
self.assertEqual(context.coordinateOperations(), {})
self.assertTrue(context.addCoordinateOperation(QgsCoordinateReferenceSystem(4204),
QgsCoordinateReferenceSystem(4326), proj_1))
self.assertTrue(context.addCoordinateOperation(QgsCoordinateReferenceSystem(4205),
QgsCoordinateReferenceSystem(4326), proj_2))
self.assertEqual(context.coordinateOperations(),
{('EPSG:4204', 'EPSG:4326'): proj_1,
('EPSG:4205', 'EPSG:4326'): proj_2})
# save to settings
context.writeSettings()
# restore from settings
context2 = QgsCoordinateTransformContext()
self.assertEqual(context2.coordinateOperations(), {})
context2.readSettings()
# check result
self.assertEqual(context2.coordinateOperations(),
{('EPSG:4204', 'EPSG:4326'): proj_1,
('EPSG:4205', 'EPSG:4326'): proj_2})
@unittest.skipIf(QgsProjUtils.projVersionMajor() >= 6, 'Skipped on proj6 builds')
def testEqualOperator(self):
context1 = QgsCoordinateTransformContext()
context2 = QgsCoordinateTransformContext()
@ -274,6 +510,20 @@ class TestQgsCoordinateTransformContext(unittest.TestCase):
QgsCoordinateReferenceSystem('EPSG:4283'), 1, 2)
self.assertTrue(context1 == context2)
@unittest.skipIf(QgsProjUtils.projVersionMajor() < 6, 'Skipped on non proj6 builds')
def testEqualOperatorProj6(self):
context1 = QgsCoordinateTransformContext()
context2 = QgsCoordinateTransformContext()
self.assertTrue(context1 == context2)
context1.addCoordinateOperation(QgsCoordinateReferenceSystem('EPSG:3111'),
QgsCoordinateReferenceSystem('EPSG:4283'), 'p1')
self.assertFalse(context1 == context2)
context2.addCoordinateOperation(QgsCoordinateReferenceSystem('EPSG:3111'),
QgsCoordinateReferenceSystem('EPSG:4283'), 'p1')
self.assertTrue(context1 == context2)
if __name__ == '__main__':
unittest.main()