26 #include <proj_experimental.h>
30 #include <QStringList>
45 const QString &error )> QgsCoordinateTransformPrivate::sCoordinateOperationCreationErrorHandler =
nullptr;
55 QgsCoordinateTransformPrivate::QgsCoordinateTransformPrivate()
64 : mSourceCRS( source )
65 , mDestCRS( destination )
67 if ( mSourceCRS != mDestCRS )
68 calculateTransforms( context );
74 : mSourceCRS( source )
75 , mDestCRS( destination )
76 , mSourceDatumTransform( sourceDatumTransform )
77 , mDestinationDatumTransform( destDatumTransform )
81 QgsCoordinateTransformPrivate::QgsCoordinateTransformPrivate(
const QgsCoordinateTransformPrivate &other )
82 : QSharedData( other )
83 , mAvailableOpCount( other.mAvailableOpCount )
84 , mIsValid( other.mIsValid )
85 , mShortCircuit( other.mShortCircuit )
86 , mGeographicToWebMercator( other.mGeographicToWebMercator )
87 , mSourceCRS( other.mSourceCRS )
88 , mDestCRS( other.mDestCRS )
89 , mSourceDatumTransform( other.mSourceDatumTransform )
90 , mDestinationDatumTransform( other.mDestinationDatumTransform )
91 , mProjCoordinateOperation( other.mProjCoordinateOperation )
92 , mShouldReverseCoordinateOperation( other.mShouldReverseCoordinateOperation )
93 , mAllowFallbackTransforms( other.mAllowFallbackTransforms )
94 , mSourceIsDynamic( other.mSourceIsDynamic )
95 , mDestIsDynamic( other.mDestIsDynamic )
96 , mSourceCoordinateEpoch( other.mSourceCoordinateEpoch )
97 , mDestCoordinateEpoch( other.mDestCoordinateEpoch )
98 , mDefaultTime( other.mDefaultTime )
99 , mIsReversed( other.mIsReversed )
102 , mProjFallbackProjections()
108 QgsCoordinateTransformPrivate::~QgsCoordinateTransformPrivate()
115 bool QgsCoordinateTransformPrivate::checkValidity()
117 if ( !mSourceCRS.isValid() || !mDestCRS.isValid() )
125 void QgsCoordinateTransformPrivate::invalidate()
127 mShortCircuit =
true;
129 mAvailableOpCount = -1;
132 bool QgsCoordinateTransformPrivate::initialize()
135 if ( !mSourceCRS.isValid() )
143 if ( !mDestCRS.isValid() )
147 mDestCRS = mSourceCRS;
154 if ( mSourceCRS == mDestCRS )
158 mShortCircuit =
true;
162 mGeographicToWebMercator =
163 mSourceCRS.isGeographic() &&
164 mDestCRS.authid() == QLatin1String(
"EPSG:3857" );
166 mSourceIsDynamic = mSourceCRS.isDynamic();
167 mSourceCoordinateEpoch = mSourceCRS.coordinateEpoch();
168 mDestIsDynamic = mDestCRS.isDynamic();
169 mDestCoordinateEpoch = mDestCRS.coordinateEpoch();
176 mDefaultTime = ( mSourceIsDynamic && !std::isnan( mSourceCoordinateEpoch ) && !mDestIsDynamic )
177 ? mSourceCoordinateEpoch
178 : ( mDestIsDynamic && !std::isnan( mDestCoordinateEpoch ) && !mSourceIsDynamic )
179 ? mDestCoordinateEpoch : std::numeric_limits< double >::quiet_NaN();
181 if ( mSourceIsDynamic && mDestIsDynamic && !
qgsNanCompatibleEquals( mSourceCoordinateEpoch, mDestCoordinateEpoch ) )
184 if ( sDynamicCrsToDynamicCrsWarningHandler )
186 sDynamicCrsToDynamicCrsWarningHandler( mSourceCRS, mDestCRS );
194 ProjData res = threadLocalProjData();
196 #ifdef COORDINATE_TRANSFORM_VERBOSE
197 QgsDebugMsg(
"From proj : " + mSourceCRS.toProj() );
204 #ifdef COORDINATE_TRANSFORM_VERBOSE
207 QgsDebugMsg( QStringLiteral(
"------------------------------------------------------------" ) );
208 QgsDebugMsg( QStringLiteral(
"The OGR Coordinate transformation for this layer was set to" ) );
209 QgsLogger::debug<QgsCoordinateReferenceSystem>(
"Input", mSourceCRS, __FILE__, __FUNCTION__, __LINE__ );
210 QgsLogger::debug<QgsCoordinateReferenceSystem>(
"Output", mDestCRS, __FILE__, __FUNCTION__, __LINE__ );
211 QgsDebugMsg( QStringLiteral(
"------------------------------------------------------------" ) );
215 QgsDebugMsg( QStringLiteral(
"------------------------------------------------------------" ) );
216 QgsDebugMsg( QStringLiteral(
"The OGR Coordinate transformation FAILED TO INITIALIZE!" ) );
217 QgsDebugMsg( QStringLiteral(
"------------------------------------------------------------" ) );
222 QgsDebugMsg( QStringLiteral(
"Coordinate transformation failed to initialize!" ) );
227 mShortCircuit =
false;
235 if ( mSourceCRS.isValid() && mDestCRS.isValid() )
243 mProjCoordinateOperation.clear();
244 mShouldReverseCoordinateOperation =
false;
245 mAllowFallbackTransforms =
false;
249 static void proj_collecting_logger(
void *user_data,
int ,
const char *message )
251 QStringList *dest =
reinterpret_cast< QStringList *
>( user_data );
252 dest->append( QString( message ) );
255 static void proj_logger(
void *,
int level,
const char *message )
260 if ( level == PJ_LOG_ERROR )
264 else if ( level == PJ_LOG_DEBUG )
270 ProjData QgsCoordinateTransformPrivate::threadLocalProjData()
275 const QMap < uintptr_t, ProjData >::const_iterator it = mProjProjections.constFind(
reinterpret_cast< uintptr_t
>( context ) );
277 if ( it != mProjProjections.constEnd() )
279 ProjData res = it.value();
287 QStringList projErrors;
288 proj_log_func( context, &projErrors, proj_collecting_logger );
293 if ( !mProjCoordinateOperation.isEmpty() )
295 transform.reset( proj_create( context, mProjCoordinateOperation.toUtf8().constData() ) );
304 #
if PROJ_VERSION_MAJOR >= 7
306 proj_context_is_network_enabled( context ) &&
307 !proj_coordoperation_is_instantiable( context, transform.get() ) )
311 if ( sMissingGridUsedByContextHandler )
314 desired.
proj = mProjCoordinateOperation;
317 sMissingGridUsedByContextHandler( mSourceCRS, mDestCRS, desired );
321 const QString err = QObject::tr(
"Could not use operation specified in project between %1 and %2. (Wanted to use: %3)." ).arg( mSourceCRS.authid(),
323 mProjCoordinateOperation );
331 mIsReversed = mShouldReverseCoordinateOperation;
335 QString nonAvailableError;
338 if ( !mSourceCRS.projObject() || ! mDestCRS.projObject() )
340 proj_log_func( context,
nullptr,
nullptr );
344 PJ_OPERATION_FACTORY_CONTEXT *operationContext = proj_create_operation_factory_context( context,
nullptr );
347 proj_operation_factory_context_set_grid_availability_use( context, operationContext, PROJ_GRID_AVAILABILITY_IGNORED );
350 proj_operation_factory_context_set_spatial_criterion( context, operationContext, PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION );
352 if ( PJ_OBJ_LIST *ops = proj_create_operations( context, mSourceCRS.projObject(), mDestCRS.projObject(), operationContext ) )
354 mAvailableOpCount = proj_list_get_count( ops );
355 if ( mAvailableOpCount < 1 )
358 const int errNo = proj_context_errno( context );
359 if ( errNo && errNo != -61 )
361 nonAvailableError = QString( proj_errno_string( errNo ) );
365 nonAvailableError = QObject::tr(
"No coordinate operations are available between these two reference systems" );
368 else if ( mAvailableOpCount == 1 )
371 transform.reset( proj_list_get( context, ops, 0 ) );
374 if ( !proj_coordoperation_is_instantiable( context, transform.get() ) )
377 for (
int j = 0; j < proj_coordoperation_get_grid_used_count( context, transform.get() ); ++j )
379 const char *shortName =
nullptr;
380 const char *fullName =
nullptr;
381 const char *packageName =
nullptr;
382 const char *url =
nullptr;
383 int directDownload = 0;
386 proj_coordoperation_get_grid_used( context, transform.get(), j, &shortName, &fullName, &packageName, &url, &directDownload, &openLicense, &isAvailable );
390 if ( sMissingRequiredGridHandler )
393 gridDetails.
shortName = QString( shortName );
394 gridDetails.
fullName = QString( fullName );
396 gridDetails.
url = QString( url );
400 sMissingRequiredGridHandler( mSourceCRS, mDestCRS, gridDetails );
404 const QString err = QObject::tr(
"Cannot create transform between %1 and %2, missing required grid %3" ).arg( mSourceCRS.authid(),
417 transform.reset( proj_normalize_for_visualization( context, transform.get() ) );
420 const QString err = QObject::tr(
"Cannot normalize transform between %1 and %2" ).arg( mSourceCRS.authid(),
431 bool missingPreferred =
false;
432 bool stillLookingForPreferred =
true;
433 for (
int i = 0; i < mAvailableOpCount; ++ i )
435 transform.reset( proj_list_get( context, ops, i ) );
436 const bool isInstantiable = transform && proj_coordoperation_is_instantiable( context, transform.get() );
437 if ( stillLookingForPreferred && transform && !isInstantiable )
441 if ( !candidate.
proj.isEmpty() )
443 preferred = candidate;
444 missingPreferred =
true;
445 stillLookingForPreferred =
false;
448 if ( transform && isInstantiable )
456 if ( transform && missingPreferred )
460 if ( sMissingPreferredGridHandler )
462 sMissingPreferredGridHandler( mSourceCRS, mDestCRS, preferred, available );
466 const QString err = QObject::tr(
"Using non-preferred coordinate operation between %1 and %2. Using %3, preferred %4." ).arg( mSourceCRS.authid(),
476 transform.reset( proj_normalize_for_visualization( context, transform.get() ) );
479 const QString err = QObject::tr(
"Cannot normalize transform between %1 and %2" ).arg( mSourceCRS.authid(),
484 proj_list_destroy( ops );
486 proj_operation_factory_context_destroy( operationContext );
489 if ( !transform && nonAvailableError.isEmpty() )
491 const int errNo = proj_context_errno( context );
492 if ( errNo && errNo != -61 )
494 nonAvailableError = QString( proj_errno_string( errNo ) );
496 else if ( !projErrors.empty() )
498 nonAvailableError = projErrors.constLast();
501 if ( nonAvailableError.isEmpty() )
503 nonAvailableError = QObject::tr(
"No coordinate operations are available between these two reference systems" );
508 nonAvailableError = nonAvailableError.remove( QStringLiteral(
"internal_proj_create_operations: " ) );
512 if ( !nonAvailableError.isEmpty() )
514 if ( sCoordinateOperationCreationErrorHandler )
516 sCoordinateOperationCreationErrorHandler( mSourceCRS, mDestCRS, nonAvailableError );
520 const QString err = QObject::tr(
"Cannot create transform between %1 and %2: %3" ).arg( mSourceCRS.authid(),
528 proj_log_func( context,
nullptr, proj_logger );
536 ProjData res = transform.release();
537 mProjProjections.insert(
reinterpret_cast< uintptr_t
>( context ), res );
541 ProjData QgsCoordinateTransformPrivate::threadLocalFallbackProjData()
546 const QMap < uintptr_t, ProjData >::const_iterator it = mProjFallbackProjections.constFind(
reinterpret_cast< uintptr_t
>( context ) );
548 if ( it != mProjFallbackProjections.constEnd() )
550 ProjData res = it.value();
559 transform.reset( proj_normalize_for_visualization(
QgsProjContext::get(), transform.get() ) );
561 ProjData res = transform.release();
562 mProjFallbackProjections.insert(
reinterpret_cast< uintptr_t
>( context ), res );
568 sMissingRequiredGridHandler = handler;
573 sMissingPreferredGridHandler = handler;
578 sCoordinateOperationCreationErrorHandler = handler;
583 sMissingGridUsedByContextHandler = handler;
588 sDynamicCrsToDynamicCrsWarningHandler = handler;
591 void QgsCoordinateTransformPrivate::freeProj()
594 if ( mProjProjections.isEmpty() && mProjFallbackProjections.isEmpty() )
596 QMap < uintptr_t, ProjData >::const_iterator it = mProjProjections.constBegin();
603 PJ_CONTEXT *tmpContext = proj_context_create();
604 for ( ; it != mProjProjections.constEnd(); ++it )
606 proj_assign_context( it.value(), tmpContext );
607 proj_destroy( it.value() );
610 it = mProjFallbackProjections.constBegin();
611 for ( ; it != mProjFallbackProjections.constEnd(); ++it )
613 proj_assign_context( it.value(), tmpContext );
614 proj_destroy( it.value() );
617 proj_context_destroy( tmpContext );
618 mProjProjections.clear();
619 mProjFallbackProjections.clear();
622 bool QgsCoordinateTransformPrivate::removeObjectsBelongingToCurrentThread(
void *pj_context )
626 QMap < uintptr_t, ProjData >::iterator it = mProjProjections.find(
reinterpret_cast< uintptr_t
>( pj_context ) );
627 if ( it != mProjProjections.end() )
629 proj_destroy( it.value() );
630 mProjProjections.erase( it );
633 it = mProjFallbackProjections.find(
reinterpret_cast< uintptr_t
>( pj_context ) );
634 if ( it != mProjFallbackProjections.end() )
636 proj_destroy( it.value() );
637 mProjFallbackProjections.erase( it );
640 return mProjProjections.isEmpty();
This class 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 logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true)
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.
The QgsReadWriteLocker class is a convenience class that simplifies locking and unlocking QReadWriteL...
#define Q_NOWARN_DEPRECATED_POP
#define Q_NOWARN_DEPRECATED_PUSH
bool qgsNanCompatibleEquals(double a, double b)
Compare two doubles, treating nan values as equal.
struct projCtx_t PJ_CONTEXT
#define QgsDebugMsgLevel(str, level)