21#include <proj_experimental.h>
33using namespace Qt::StringLiterals;
38 QgsCoordinateTransformPrivate::sMissingRequiredGridHandler =
nullptr;
42 QgsCoordinateTransformPrivate::sMissingPreferredGridHandler =
nullptr;
45 QgsCoordinateTransformPrivate::sCoordinateOperationCreationErrorHandler =
nullptr;
48 QgsCoordinateTransformPrivate::sMissingGridUsedByContextHandler =
nullptr;
53 QgsCoordinateTransformPrivate::QgsCoordinateTransformPrivate()
59 : mSourceCRS( source )
60 , mDestCRS( destination )
62 if ( mSourceCRS != mDestCRS )
63 calculateTransforms( context );
69 : mSourceCRS( source )
70 , mDestCRS( destination )
71 , mSourceDatumTransform( sourceDatumTransform )
72 , mDestinationDatumTransform( destDatumTransform )
75QgsCoordinateTransformPrivate::QgsCoordinateTransformPrivate(
const QgsCoordinateTransformPrivate &other )
76 : QSharedData( other )
77 , mAvailableOpCount( other.mAvailableOpCount )
78 , mIsValid( other.mIsValid )
79 , mShortCircuit( other.mShortCircuit )
80 , mGeographicToWebMercator( other.mGeographicToWebMercator )
81 , mHasVerticalComponent( other.mHasVerticalComponent )
82 , mSourceCRS( other.mSourceCRS )
83 , mDestCRS( other.mDestCRS )
84 , mSourceDatumTransform( other.mSourceDatumTransform )
85 , mDestinationDatumTransform( other.mDestinationDatumTransform )
86 , mProjCoordinateOperation( other.mProjCoordinateOperation )
87 , mShouldReverseCoordinateOperation( other.mShouldReverseCoordinateOperation )
88 , mAllowFallbackTransforms( other.mAllowFallbackTransforms )
89 , mSourceIsDynamic( other.mSourceIsDynamic )
90 , mDestIsDynamic( other.mDestIsDynamic )
91 , mSourceCoordinateEpoch( other.mSourceCoordinateEpoch )
92 , mDestCoordinateEpoch( other.mDestCoordinateEpoch )
93 , mDefaultTime( other.mDefaultTime )
94 , mIsReversed( other.mIsReversed )
97 , mProjFallbackProjections()
102QgsCoordinateTransformPrivate::~QgsCoordinateTransformPrivate()
109bool QgsCoordinateTransformPrivate::checkValidity()
111 if ( !mSourceCRS.isValid() || !mDestCRS.isValid() )
119void QgsCoordinateTransformPrivate::invalidate()
121 mShortCircuit =
true;
123 mAvailableOpCount = -1;
126bool QgsCoordinateTransformPrivate::initialize()
129 if ( !mSourceCRS.isValid() )
137 if ( !mDestCRS.isValid() )
141 mDestCRS = mSourceCRS;
148 if ( mSourceCRS == mDestCRS )
152 mShortCircuit =
true;
156 mGeographicToWebMercator = mSourceCRS.isGeographic() && mDestCRS.authid() ==
"EPSG:3857"_L1;
158 mHasVerticalComponent = mSourceCRS.hasVerticalAxis() && mDestCRS.hasVerticalAxis();
160 mSourceIsDynamic = mSourceCRS.isDynamic();
161 mSourceCoordinateEpoch = mSourceCRS.coordinateEpoch();
162 mDestIsDynamic = mDestCRS.isDynamic();
163 mDestCoordinateEpoch = mDestCRS.coordinateEpoch();
170 mDefaultTime = ( mSourceIsDynamic && !std::isnan( mSourceCoordinateEpoch ) && !mDestIsDynamic ) ? mSourceCoordinateEpoch
171 : ( mDestIsDynamic && !std::isnan( mDestCoordinateEpoch ) && !mSourceIsDynamic ) ? mDestCoordinateEpoch
172 : std::numeric_limits< double >::quiet_NaN();
174 if ( mSourceIsDynamic && mDestIsDynamic && !
qgsNanCompatibleEquals( mSourceCoordinateEpoch, mDestCoordinateEpoch ) )
177 if ( sDynamicCrsToDynamicCrsWarningHandler )
179 sDynamicCrsToDynamicCrsWarningHandler( mSourceCRS, mDestCRS );
187 ProjData res = threadLocalProjData();
189#ifdef COORDINATE_TRANSFORM_VERBOSE
197#ifdef COORDINATE_TRANSFORM_VERBOSE
200 QgsDebugMsgLevel( u
"------------------------------------------------------------"_s, 2 );
201 QgsDebugMsgLevel( u
"The OGR Coordinate transformation for this layer was set to"_s, 2 );
204 QgsDebugMsgLevel( u
"------------------------------------------------------------"_s, 2 );
208 QgsDebugError( u
"The OGR Coordinate transformation FAILED TO INITIALIZE!"_s );
213 QgsDebugError( u
"Coordinate transformation failed to initialize!"_s );
218 mShortCircuit =
false;
226 if ( mSourceCRS.isValid() && mDestCRS.isValid() )
234 mProjCoordinateOperation.clear();
235 mShouldReverseCoordinateOperation =
false;
236 mAllowFallbackTransforms =
false;
240ProjData QgsCoordinateTransformPrivate::threadLocalProjData()
245 const QMap< uintptr_t, ProjData >::const_iterator it = mProjProjections.constFind(
reinterpret_cast< uintptr_t
>( context ) );
247 if ( it != mProjProjections.constEnd() )
249 ProjData res = it.value();
262 if ( !mProjCoordinateOperation.isEmpty() )
264 transform.reset( proj_create( context, mProjCoordinateOperation.toUtf8().constData() ) );
272 if ( !transform || ( proj_context_is_network_enabled( context ) && !proj_coordoperation_is_instantiable( context, transform.get() ) ) )
274 if ( sMissingGridUsedByContextHandler )
277 desired.
proj = mProjCoordinateOperation;
280 sMissingGridUsedByContextHandler( mSourceCRS, mDestCRS, desired );
284 const QString err = QObject::tr(
"Could not use operation specified in project between %1 and %2. (Wanted to use: %3)." ).arg( mSourceCRS.authid(), mDestCRS.authid(), mProjCoordinateOperation );
292 mIsReversed = mShouldReverseCoordinateOperation;
296 QString nonAvailableError;
299 if ( !mSourceCRS.projObject() || !mDestCRS.projObject() )
304 PJ_OPERATION_FACTORY_CONTEXT *operationContext = proj_create_operation_factory_context( context,
nullptr );
307 proj_operation_factory_context_set_grid_availability_use( context, operationContext, PROJ_GRID_AVAILABILITY_IGNORED );
310 proj_operation_factory_context_set_spatial_criterion( context, operationContext, PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION );
312 if ( PJ_OBJ_LIST *ops = proj_create_operations( context, mSourceCRS.projObject(), mDestCRS.projObject(), operationContext ) )
314 mAvailableOpCount = proj_list_get_count( ops );
315 if ( mAvailableOpCount < 1 )
318 const int errNo = proj_context_errno( context );
321 nonAvailableError = QString( proj_context_errno_string( context, errNo ) );
326 nonAvailableError = QObject::tr(
"No coordinate operations are available between these two reference systems" );
329 else if ( mAvailableOpCount == 1 )
332 transform.reset( proj_list_get( context, ops, 0 ) );
335 if ( !proj_coordoperation_is_instantiable( context, transform.get() ) )
338 for (
int j = 0; j < proj_coordoperation_get_grid_used_count( context, transform.get() ); ++j )
340 const char *shortName =
nullptr;
341 const char *fullName =
nullptr;
342 const char *packageName =
nullptr;
343 const char *url =
nullptr;
344 int directDownload = 0;
347 proj_coordoperation_get_grid_used( context, transform.get(), j, &shortName, &fullName, &packageName, &url, &directDownload, &openLicense, &isAvailable );
351 if ( sMissingRequiredGridHandler )
354 gridDetails.
shortName = QString( shortName );
355 gridDetails.
fullName = QString( fullName );
357 gridDetails.
url = QString( url );
361 sMissingRequiredGridHandler( mSourceCRS, mDestCRS, gridDetails );
365 const QString err = QObject::tr(
"Cannot create transform between %1 and %2, missing required grid %3" ).arg( mSourceCRS.authid(), mDestCRS.authid(), shortName );
375 transform.reset( proj_normalize_for_visualization( context, transform.get() ) );
378 const QString err = QObject::tr(
"Cannot normalize transform between %1 and %2" ).arg( mSourceCRS.authid(), mDestCRS.authid() );
388 bool missingPreferred =
false;
389 bool stillLookingForPreferred =
true;
390 for (
int i = 0; i < mAvailableOpCount; ++i )
392 transform.reset( proj_list_get( context, ops, i ) );
393 const bool isInstantiable = transform && proj_coordoperation_is_instantiable( context, transform.get() );
394 if ( stillLookingForPreferred && transform && !isInstantiable )
398 if ( !candidate.
proj.isEmpty() )
400 preferred = candidate;
401 missingPreferred =
true;
402 stillLookingForPreferred =
false;
405 if ( transform && isInstantiable )
413 if ( transform && missingPreferred )
417 if ( sMissingPreferredGridHandler )
419 sMissingPreferredGridHandler( mSourceCRS, mDestCRS, preferred, available );
424 = QObject::tr(
"Using non-preferred coordinate operation between %1 and %2. Using %3, preferred %4." ).arg( mSourceCRS.authid(), mDestCRS.authid(), available.
proj, preferred.
proj );
431 transform.reset( proj_normalize_for_visualization( context, transform.get() ) );
434 const QString err = QObject::tr(
"Cannot normalize transform between %1 and %2" ).arg( mSourceCRS.authid(), mDestCRS.authid() );
438 proj_list_destroy( ops );
440 proj_operation_factory_context_destroy( operationContext );
443 if ( !transform && nonAvailableError.isEmpty() )
445 const int errNo = proj_context_errno( context );
446 const QStringList projErrors = errorLogger.
errors();
449 nonAvailableError = QString( proj_context_errno_string( context, errNo ) );
451 else if ( !projErrors.empty() )
453 nonAvailableError = projErrors.constLast();
456 if ( nonAvailableError.isEmpty() )
458 nonAvailableError = QObject::tr(
"No coordinate operations are available between these two reference systems" );
463 nonAvailableError = nonAvailableError.remove( u
"internal_proj_create_operations: "_s );
467 if ( !nonAvailableError.isEmpty() )
469 if ( sCoordinateOperationCreationErrorHandler )
471 sCoordinateOperationCreationErrorHandler( mSourceCRS, mDestCRS, nonAvailableError );
475 const QString err = QObject::tr(
"Cannot create transform between %1 and %2: %3" ).arg( mSourceCRS.authid(), mDestCRS.authid(), nonAvailableError );
486 ProjData res = transform.release();
487 mProjProjections.insert(
reinterpret_cast< uintptr_t
>( context ), res );
491ProjData QgsCoordinateTransformPrivate::threadLocalFallbackProjData()
496 const QMap< uintptr_t, ProjData >::const_iterator it = mProjFallbackProjections.constFind(
reinterpret_cast< uintptr_t
>( context ) );
498 if ( it != mProjFallbackProjections.constEnd() )
500 ProjData res = it.value();
509 transform.reset( proj_normalize_for_visualization(
QgsProjContext::get(), transform.get() ) );
511 ProjData res = transform.release();
512 mProjFallbackProjections.insert(
reinterpret_cast< uintptr_t
>( context ), res );
516void QgsCoordinateTransformPrivate::setCustomMissingRequiredGridHandler(
520 sMissingRequiredGridHandler = handler;
523void QgsCoordinateTransformPrivate::setCustomMissingPreferredGridHandler(
527 sMissingPreferredGridHandler = handler;
530void QgsCoordinateTransformPrivate::setCustomCoordinateOperationCreationErrorHandler(
534 sCoordinateOperationCreationErrorHandler = handler;
537void QgsCoordinateTransformPrivate::setCustomMissingGridUsedByContextHandler(
541 sMissingGridUsedByContextHandler = handler;
546 sDynamicCrsToDynamicCrsWarningHandler = handler;
549void QgsCoordinateTransformPrivate::freeProj()
552 if ( mProjProjections.isEmpty() && mProjFallbackProjections.isEmpty() )
554 QMap< uintptr_t, ProjData >::const_iterator it = mProjProjections.constBegin();
561 PJ_CONTEXT *tmpContext = proj_context_create();
562 for ( ; it != mProjProjections.constEnd(); ++it )
564 proj_assign_context( it.value(), tmpContext );
565 proj_destroy( it.value() );
568 it = mProjFallbackProjections.constBegin();
569 for ( ; it != mProjFallbackProjections.constEnd(); ++it )
571 proj_assign_context( it.value(), tmpContext );
572 proj_destroy( it.value() );
575 proj_context_destroy( tmpContext );
576 mProjProjections.clear();
577 mProjFallbackProjections.clear();
580bool QgsCoordinateTransformPrivate::removeObjectsBelongingToCurrentThread(
void *pj_context )
584 QMap< uintptr_t, ProjData >::iterator it = mProjProjections.find(
reinterpret_cast< uintptr_t
>( pj_context ) );
585 if ( it != mProjProjections.end() )
587 proj_destroy( it.value() );
588 mProjProjections.erase( it );
591 it = mProjFallbackProjections.find(
reinterpret_cast< uintptr_t
>( pj_context ) );
592 if ( it != mProjFallbackProjections.end() )
594 proj_destroy( it.value() );
595 mProjFallbackProjections.erase( it );
598 return mProjProjections.isEmpty();
@ Critical
Critical/error message.
Represents a coordinate reference system (CRS).
Contains information about the context in which a coordinate transform is executed.
bool allowFallbackTransform(const QgsCoordinateReferenceSystem &source, const QgsCoordinateReferenceSystem &destination) const
Returns true if approximate "ballpark" transforms may be used when transforming between a source and ...
QString calculateCoordinateOperation(const QgsCoordinateReferenceSystem &source, const QgsCoordinateReferenceSystem &destination) const
Returns the Proj coordinate operation string to use when transforming from the specified source CRS t...
bool mustReverseCoordinateOperation(const QgsCoordinateReferenceSystem &source, const QgsCoordinateReferenceSystem &destination) const
Returns true if the coordinate operation returned by calculateCoordinateOperation() for the source to...
static void debug(const QString &msg, int debuglevel=1, const char *file=nullptr, const char *function=nullptr, int line=-1)
Goes to qDebug.
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true, const char *file=__builtin_FILE(), const char *function=__builtin_FUNCTION(), int line=__builtin_LINE(), Qgis::StringFormat format=Qgis::StringFormat::PlainText)
Adds a message to the log instance (and creates it if necessary).
static PJ_CONTEXT * get()
Returns a thread local instance of a proj context, safe for use in the current thread.
static QList< QgsDatumTransform::GridDetails > gridsUsed(const QString &proj)
Returns a list of grids used by the given proj string.
std::unique_ptr< PJ, ProjPJDeleter > proj_pj_unique_ptr
Scoped Proj PJ object.
A convenience class that simplifies locking and unlocking QReadWriteLocks.
Scoped object for temporary swapping to an error-collecting PROJ log function.
QStringList errors() const
Returns the (possibly empty) list of collected errors.
#define Q_NOWARN_DEPRECATED_POP
#define Q_NOWARN_DEPRECATED_PUSH
bool qgsNanCompatibleEquals(double a, double b)
Compare two doubles, treating nan values as equal.
#define QgsDebugMsgLevel(str, level)
#define QgsDebugError(str)