24 #if PROJ_VERSION_MAJOR>=6
27 #include <proj_experimental.h>
34 #include <QStringList>
49 const QString &error )> QgsCoordinateTransformPrivate::sCoordinateOperationCreationErrorHandler =
nullptr;
55 #if PROJ_VERSION_MAJOR<6
56 #ifdef USE_THREAD_LOCAL
57 thread_local QgsProjContextStore QgsCoordinateTransformPrivate::mProjContext;
59 QThreadStorage< QgsProjContextStore * > QgsCoordinateTransformPrivate::mProjContext;
62 QgsProjContextStore::QgsProjContextStore()
64 context = pj_ctx_alloc();
67 QgsProjContextStore::~QgsProjContextStore()
69 pj_ctx_free( context );
75 QgsCoordinateTransformPrivate::QgsCoordinateTransformPrivate()
84 : mSourceCRS( source )
85 , mDestCRS( destination )
87 if ( mSourceCRS != mDestCRS )
88 calculateTransforms( context );
94 : mSourceCRS( source )
95 , mDestCRS( destination )
96 , mSourceDatumTransform( sourceDatumTransform )
97 , mDestinationDatumTransform( destDatumTransform )
101 QgsCoordinateTransformPrivate::QgsCoordinateTransformPrivate(
const QgsCoordinateTransformPrivate &other )
102 : QSharedData( other )
103 #if PROJ_VERSION_MAJOR >= 6
104 , mAvailableOpCount( other.mAvailableOpCount )
106 , mIsValid( other.mIsValid )
107 , mShortCircuit( other.mShortCircuit )
108 , mSourceCRS( other.mSourceCRS )
109 , mDestCRS( other.mDestCRS )
110 , mSourceDatumTransform( other.mSourceDatumTransform )
111 , mDestinationDatumTransform( other.mDestinationDatumTransform )
112 , mProjCoordinateOperation( other.mProjCoordinateOperation )
113 , mShouldReverseCoordinateOperation( other.mShouldReverseCoordinateOperation )
114 , mAllowFallbackTransforms( other.mAllowFallbackTransforms )
115 , mIsReversed( other.mIsReversed )
118 , mProjFallbackProjections()
120 #if PROJ_VERSION_MAJOR < 6
128 QgsCoordinateTransformPrivate::~QgsCoordinateTransformPrivate()
135 bool QgsCoordinateTransformPrivate::checkValidity()
137 if ( !mSourceCRS.isValid() || !mDestCRS.isValid() )
145 void QgsCoordinateTransformPrivate::invalidate()
147 mShortCircuit =
true;
149 #if PROJ_VERSION_MAJOR >= 6
150 mAvailableOpCount = -1;
154 bool QgsCoordinateTransformPrivate::initialize()
157 if ( !mSourceCRS.isValid() )
165 if ( !mDestCRS.isValid() )
169 mDestCRS = mSourceCRS;
176 if ( mSourceCRS == mDestCRS )
180 mShortCircuit =
true;
187 #if PROJ_VERSION_MAJOR < 6
189 int sourceDatumTransform = mSourceDatumTransform;
190 int destDatumTransform = mDestinationDatumTransform;
191 bool useDefaultDatumTransform = ( sourceDatumTransform == - 1 && destDatumTransform == -1 );
193 mSourceProjString = mSourceCRS.toProj();
194 if ( !useDefaultDatumTransform )
196 mSourceProjString = stripDatumTransform( mSourceProjString );
198 if ( sourceDatumTransform != -1 )
203 mDestProjString = mDestCRS.toProj();
204 if ( !useDefaultDatumTransform )
206 mDestProjString = stripDatumTransform( mDestProjString );
208 if ( destDatumTransform != -1 )
213 if ( !useDefaultDatumTransform )
215 addNullGridShifts( mSourceProjString, mDestProjString, sourceDatumTransform, destDatumTransform );
221 ProjData res = threadLocalProjData();
223 #ifdef COORDINATE_TRANSFORM_VERBOSE
224 QgsDebugMsg(
"From proj : " + mSourceCRS.toProj() );
228 #if PROJ_VERSION_MAJOR>=6
232 if ( !res.first || !res.second )
238 #ifdef COORDINATE_TRANSFORM_VERBOSE
241 QgsDebugMsg( QStringLiteral(
"------------------------------------------------------------" ) );
242 QgsDebugMsg( QStringLiteral(
"The OGR Coordinate transformation for this layer was set to" ) );
243 QgsLogger::debug<QgsCoordinateReferenceSystem>(
"Input", mSourceCRS, __FILE__, __FUNCTION__, __LINE__ );
244 QgsLogger::debug<QgsCoordinateReferenceSystem>(
"Output", mDestCRS, __FILE__, __FUNCTION__, __LINE__ );
245 QgsDebugMsg( QStringLiteral(
"------------------------------------------------------------" ) );
249 QgsDebugMsg( QStringLiteral(
"------------------------------------------------------------" ) );
250 QgsDebugMsg( QStringLiteral(
"The OGR Coordinate transformation FAILED TO INITIALIZE!" ) );
251 QgsDebugMsg( QStringLiteral(
"------------------------------------------------------------" ) );
256 QgsDebugMsg( QStringLiteral(
"Coordinate transformation failed to initialize!" ) );
261 mShortCircuit =
false;
269 #if PROJ_VERSION_MAJOR >= 6
270 if ( mSourceCRS.isValid() && mDestCRS.isValid() )
278 mProjCoordinateOperation.clear();
279 mShouldReverseCoordinateOperation =
false;
280 mAllowFallbackTransforms =
false;
286 mDestinationDatumTransform = transforms.destinationTransformId;
291 #if PROJ_VERSION_MAJOR>=6
292 static void proj_collecting_logger(
void *user_data,
int ,
const char *message )
294 QStringList *dest =
reinterpret_cast< QStringList *
>( user_data );
295 dest->append( QString( message ) );
298 static void proj_logger(
void *,
int level,
const char *message )
303 if ( level == PJ_LOG_ERROR )
307 else if ( level == PJ_LOG_DEBUG )
314 ProjData QgsCoordinateTransformPrivate::threadLocalProjData()
318 #if PROJ_VERSION_MAJOR>=6
320 QMap < uintptr_t, ProjData >::const_iterator it = mProjProjections.constFind(
reinterpret_cast< uintptr_t
>( context ) );
322 #ifdef USE_THREAD_LOCAL
323 QMap < uintptr_t, QPair< projPJ, projPJ > >::const_iterator it = mProjProjections.constFind(
reinterpret_cast< uintptr_t
>( mProjContext.get() ) );
325 projCtx pContext =
nullptr;
326 if ( mProjContext.hasLocalData() )
328 pContext = mProjContext.localData()->get();
332 mProjContext.setLocalData(
new QgsProjContextStore() );
333 pContext = mProjContext.localData()->get();
335 QMap < uintptr_t, QPair< projPJ, projPJ > >::const_iterator it = mProjProjections.constFind(
reinterpret_cast< uintptr_t
>( pContext ) );
339 if ( it != mProjProjections.constEnd() )
341 ProjData res = it.value();
348 #if PROJ_VERSION_MAJOR>=6
350 QStringList projErrors;
351 proj_log_func( context, &projErrors, proj_collecting_logger );
355 QgsProjUtils::proj_pj_unique_ptr transform;
356 if ( !mProjCoordinateOperation.isEmpty() )
358 transform.reset( proj_create( context, mProjCoordinateOperation.toUtf8().constData() ) );
359 if ( !transform || !proj_coordoperation_is_instantiable( context, transform.get() ) )
361 if ( sMissingGridUsedByContextHandler )
364 desired.
proj = mProjCoordinateOperation;
366 desired.
grids = QgsProjUtils::gridsUsed( mProjCoordinateOperation );
367 sMissingGridUsedByContextHandler( mSourceCRS, mDestCRS, desired );
371 const QString err = QObject::tr(
"Could not use operation specified in project between %1 and %2. (Wanted to use: %3)." ).arg( mSourceCRS.authid(),
373 mProjCoordinateOperation );
381 mIsReversed = mShouldReverseCoordinateOperation;
385 QString nonAvailableError;
388 if ( !mSourceCRS.projObject() || ! mDestCRS.projObject() )
390 proj_log_func( context,
nullptr,
nullptr );
394 PJ_OPERATION_FACTORY_CONTEXT *operationContext = proj_create_operation_factory_context( context,
nullptr );
397 proj_operation_factory_context_set_grid_availability_use( context, operationContext, PROJ_GRID_AVAILABILITY_IGNORED );
400 proj_operation_factory_context_set_spatial_criterion( context, operationContext, PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION );
402 if ( PJ_OBJ_LIST *ops = proj_create_operations( context, mSourceCRS.projObject(), mDestCRS.projObject(), operationContext ) )
404 mAvailableOpCount = proj_list_get_count( ops );
405 if ( mAvailableOpCount < 1 )
408 int errNo = proj_context_errno( context );
409 if ( errNo && errNo != -61 )
411 nonAvailableError = QString( proj_errno_string( errNo ) );
415 nonAvailableError = QObject::tr(
"No coordinate operations are available between these two reference systems" );
418 else if ( mAvailableOpCount == 1 )
421 transform.reset( proj_list_get( context, ops, 0 ) );
424 if ( !proj_coordoperation_is_instantiable( context, transform.get() ) )
427 for (
int j = 0; j < proj_coordoperation_get_grid_used_count( context, transform.get() ); ++j )
429 const char *shortName =
nullptr;
430 const char *fullName =
nullptr;
431 const char *packageName =
nullptr;
432 const char *url =
nullptr;
433 int directDownload = 0;
436 proj_coordoperation_get_grid_used( context, transform.get(), j, &shortName, &fullName, &packageName, &url, &directDownload, &openLicense, &isAvailable );
440 if ( sMissingRequiredGridHandler )
443 gridDetails.
shortName = QString( shortName );
444 gridDetails.
fullName = QString( fullName );
446 gridDetails.
url = QString( url );
450 sMissingRequiredGridHandler( mSourceCRS, mDestCRS, gridDetails );
454 const QString err = QObject::tr(
"Cannot create transform between %1 and %2, missing required grid %3" ).arg( mSourceCRS.authid(),
467 transform.reset( proj_normalize_for_visualization( context, transform.get() ) );
470 const QString err = QObject::tr(
"Cannot normalize transform between %1 and %2" ).arg( mSourceCRS.authid(),
481 bool missingPreferred =
false;
482 bool stillLookingForPreferred =
true;
483 for (
int i = 0; i < mAvailableOpCount; ++ i )
485 transform.reset( proj_list_get( context, ops, i ) );
486 const bool isInstantiable = transform && proj_coordoperation_is_instantiable( context, transform.get() );
487 if ( stillLookingForPreferred && transform && !isInstantiable )
491 if ( !candidate.
proj.isEmpty() )
493 preferred = candidate;
494 missingPreferred =
true;
495 stillLookingForPreferred =
false;
498 if ( transform && isInstantiable )
506 if ( transform && missingPreferred )
510 if ( sMissingPreferredGridHandler )
512 sMissingPreferredGridHandler( mSourceCRS, mDestCRS, preferred, available );
516 const QString err = QObject::tr(
"Using non-preferred coordinate operation between %1 and %2. Using %3, preferred %4." ).arg( mSourceCRS.authid(),
526 transform.reset( proj_normalize_for_visualization( context, transform.get() ) );
529 const QString err = QObject::tr(
"Cannot normalize transform between %1 and %2" ).arg( mSourceCRS.authid(),
534 proj_list_destroy( ops );
536 proj_operation_factory_context_destroy( operationContext );
539 if ( !transform && nonAvailableError.isEmpty() )
541 int errNo = proj_context_errno( context );
542 if ( errNo && errNo != -61 )
544 nonAvailableError = QString( proj_errno_string( errNo ) );
546 else if ( !projErrors.empty() )
548 nonAvailableError = projErrors.constLast();
551 if ( nonAvailableError.isEmpty() )
553 nonAvailableError = QObject::tr(
"No coordinate operations are available between these two reference systems" );
558 nonAvailableError = nonAvailableError.remove( QStringLiteral(
"internal_proj_create_operations: " ) );
562 if ( !nonAvailableError.isEmpty() )
564 if ( sCoordinateOperationCreationErrorHandler )
566 sCoordinateOperationCreationErrorHandler( mSourceCRS, mDestCRS, nonAvailableError );
570 const QString err = QObject::tr(
"Cannot create transform between %1 and %2: %3" ).arg( mSourceCRS.authid(),
578 proj_log_func( context,
nullptr, proj_logger );
586 ProjData res = transform.release();
587 mProjProjections.insert(
reinterpret_cast< uintptr_t
>( context ), res );
589 #ifdef USE_THREAD_LOCAL
591 QPair<projPJ, projPJ> res = qMakePair( pj_init_plus_ctx( mProjContext.get(), mSourceProjString.toUtf8() ),
592 pj_init_plus_ctx( mProjContext.get(), mDestProjString.toUtf8() ) );
594 mProjProjections.insert(
reinterpret_cast< uintptr_t
>( mProjContext.get() ), res );
596 QPair<projPJ, projPJ> res = qMakePair( pj_init_plus_ctx( pContext, mSourceProjString.toUtf8() ),
597 pj_init_plus_ctx( pContext, mDestProjString.toUtf8() ) );
598 mProjProjections.insert(
reinterpret_cast< uintptr_t
>( pContext ), res );
604 #if PROJ_VERSION_MAJOR>=6
605 ProjData QgsCoordinateTransformPrivate::threadLocalFallbackProjData()
610 QMap < uintptr_t, ProjData >::const_iterator it = mProjFallbackProjections.constFind(
reinterpret_cast< uintptr_t
>( context ) );
612 if ( it != mProjFallbackProjections.constEnd() )
614 ProjData res = it.value();
621 QgsProjUtils::proj_pj_unique_ptr transform( proj_create_crs_to_crs_from_pj( context, mSourceCRS.projObject(), mDestCRS.projObject(),
nullptr,
nullptr ) );
623 transform.reset( proj_normalize_for_visualization(
QgsProjContext::get(), transform.get() ) );
625 ProjData res = transform.release();
626 mProjFallbackProjections.insert(
reinterpret_cast< uintptr_t
>( context ), res );
633 sMissingRequiredGridHandler = handler;
638 sMissingPreferredGridHandler = handler;
643 sCoordinateOperationCreationErrorHandler = handler;
648 sMissingGridUsedByContextHandler = handler;
651 #if PROJ_VERSION_MAJOR<6
652 QString QgsCoordinateTransformPrivate::stripDatumTransform(
const QString &proj4 )
const
654 QStringList parameterSplit = proj4.split(
'+', QString::SkipEmptyParts );
655 QString currentParameter;
656 QString newProjString;
658 for (
int i = 0; i < parameterSplit.size(); ++i )
660 currentParameter = parameterSplit.at( i );
661 if ( !currentParameter.startsWith( QLatin1String(
"towgs84" ), Qt::CaseInsensitive )
662 && !currentParameter.startsWith( QLatin1String(
"nadgrids" ), Qt::CaseInsensitive ) )
664 newProjString.append(
'+' );
665 newProjString.append( currentParameter );
666 newProjString.append(
' ' );
669 return newProjString;
672 void QgsCoordinateTransformPrivate::addNullGridShifts( QString &srcProjString, QString &destProjString,
673 int sourceDatumTransform,
int destinationDatumTransform )
const
676 if ( destinationDatumTransform == -1 && srcProjString.contains( QLatin1String(
"+nadgrids" ) ) )
678 destProjString += QLatin1String(
" +nadgrids=@null" );
681 if ( sourceDatumTransform == -1 && destProjString.contains( QLatin1String(
"+nadgrids" ) ) )
683 srcProjString += QLatin1String(
" +nadgrids=@null" );
689 if ( mSourceCRS.authid().compare( QLatin1String(
"EPSG:3857" ), Qt::CaseInsensitive ) == 0 && sourceDatumTransform == -1 )
691 srcProjString += QLatin1String(
" +nadgrids=@null" );
693 if ( mDestCRS.authid().compare( QLatin1String(
"EPSG:3857" ), Qt::CaseInsensitive ) == 0 && destinationDatumTransform == -1 )
695 destProjString += QLatin1String(
" +nadgrids=@null" );
700 void QgsCoordinateTransformPrivate::freeProj()
703 if ( mProjProjections.isEmpty() && mProjFallbackProjections.isEmpty() )
705 QMap < uintptr_t, ProjData >::const_iterator it = mProjProjections.constBegin();
706 #if PROJ_VERSION_MAJOR>=6
712 PJ_CONTEXT *tmpContext = proj_context_create();
713 for ( ; it != mProjProjections.constEnd(); ++it )
715 proj_assign_context( it.value(), tmpContext );
716 proj_destroy( it.value() );
719 it = mProjFallbackProjections.constBegin();
720 for ( ; it != mProjFallbackProjections.constEnd(); ++it )
722 proj_assign_context( it.value(), tmpContext );
723 proj_destroy( it.value() );
726 proj_context_destroy( tmpContext );
728 projCtx tmpContext = pj_ctx_alloc();
729 for ( ; it != mProjProjections.constEnd(); ++it )
731 pj_set_ctx( it.value().first, tmpContext );
732 pj_free( it.value().first );
733 pj_set_ctx( it.value().second, tmpContext );
734 pj_free( it.value().second );
736 pj_ctx_free( tmpContext );
738 mProjProjections.clear();
739 mProjFallbackProjections.clear();
742 #if PROJ_VERSION_MAJOR>=6
743 bool QgsCoordinateTransformPrivate::removeObjectsBelongingToCurrentThread(
void *pj_context )
747 QMap < uintptr_t, ProjData >::iterator it = mProjProjections.find(
reinterpret_cast< uintptr_t
>( pj_context ) );
748 if ( it != mProjProjections.end() )
750 proj_destroy( it.value() );
751 mProjProjections.erase( it );
754 it = mProjFallbackProjections.find(
reinterpret_cast< uintptr_t
>( pj_context ) );
755 if ( it != mProjFallbackProjections.end() )
757 proj_destroy( it.value() );
758 mProjFallbackProjections.erase( it );
761 return mProjProjections.isEmpty();