LCOV - code coverage report
Current view: top level - core - qgscoordinatetransform_p.cpp (source / functions) Hit Total Coverage
Test: coverage.info.cleaned Lines: 125 321 38.9 %
Date: 2021-04-10 08:29:14 Functions: 0 0 -
Branches: 0 0 -

           Branch data     Line data    Source code
       1                 :            : /***************************************************************************
       2                 :            :                qgscoordinatetransform_p.cpp
       3                 :            :                ----------------------------
       4                 :            :     begin                : May 2017
       5                 :            :     copyright            : (C) 2017 Nyall Dawson
       6                 :            :     email                : nyall dot dawson at gmail dot com
       7                 :            :  ***************************************************************************/
       8                 :            : 
       9                 :            : /***************************************************************************
      10                 :            :  *                                                                         *
      11                 :            :  *   This program is free software; you can redistribute it and/or modify  *
      12                 :            :  *   it under the terms of the GNU General Public License as published by  *
      13                 :            :  *   the Free Software Foundation; either version 2 of the License, or     *
      14                 :            :  *   (at your option) any later version.                                   *
      15                 :            :  *                                                                         *
      16                 :            :  ***************************************************************************/
      17                 :            : 
      18                 :            : #include "qgscoordinatetransform_p.h"
      19                 :            : #include "qgslogger.h"
      20                 :            : #include "qgsapplication.h"
      21                 :            : #include "qgsreadwritelocker.h"
      22                 :            : #include "qgsmessagelog.h"
      23                 :            : 
      24                 :            : #include "qgsprojutils.h"
      25                 :            : #include <proj.h>
      26                 :            : #include <proj_experimental.h>
      27                 :            : 
      28                 :            : #include <sqlite3.h>
      29                 :            : 
      30                 :            : #include <QStringList>
      31                 :            : 
      32                 :            : /// @cond PRIVATE
      33                 :            : 
      34                 :            : std::function< void( const QgsCoordinateReferenceSystem &sourceCrs,
      35                 :            :                      const QgsCoordinateReferenceSystem &destinationCrs,
      36                 :          5 :                      const QgsDatumTransform::GridDetails &grid )> QgsCoordinateTransformPrivate::sMissingRequiredGridHandler = nullptr;
      37                 :            : 
      38                 :            : std::function< void( const QgsCoordinateReferenceSystem &sourceCrs,
      39                 :            :                      const QgsCoordinateReferenceSystem &destinationCrs,
      40                 :            :                      const QgsDatumTransform::TransformDetails &preferredOperation,
      41                 :          5 :                      const QgsDatumTransform::TransformDetails &availableOperation )> QgsCoordinateTransformPrivate::sMissingPreferredGridHandler = nullptr;
      42                 :            : 
      43                 :            : std::function< void( const QgsCoordinateReferenceSystem &sourceCrs,
      44                 :            :                      const QgsCoordinateReferenceSystem &destinationCrs,
      45                 :          5 :                      const QString &error )> QgsCoordinateTransformPrivate::sCoordinateOperationCreationErrorHandler = nullptr;
      46                 :            : 
      47                 :            : std::function< void( const QgsCoordinateReferenceSystem &sourceCrs,
      48                 :            :                      const QgsCoordinateReferenceSystem &destinationCrs,
      49                 :          5 :                      const QgsDatumTransform::TransformDetails &desiredOperation )> QgsCoordinateTransformPrivate::sMissingGridUsedByContextHandler = nullptr;
      50                 :            : 
      51                 :            : Q_NOWARN_DEPRECATED_PUSH // because of deprecated members
      52                 :        318 : QgsCoordinateTransformPrivate::QgsCoordinateTransformPrivate()
      53                 :        318 : {
      54                 :        318 : }
      55                 :            : Q_NOWARN_DEPRECATED_POP
      56                 :            : 
      57                 :            : Q_NOWARN_DEPRECATED_PUSH // because of deprecated members
      58                 :       1533 : QgsCoordinateTransformPrivate::QgsCoordinateTransformPrivate( const QgsCoordinateReferenceSystem &source,
      59                 :            :     const QgsCoordinateReferenceSystem &destination,
      60                 :            :     const QgsCoordinateTransformContext &context )
      61                 :       1533 :   : mSourceCRS( source )
      62                 :       1533 :   , mDestCRS( destination )
      63                 :       1533 : {
      64                 :       1533 :   if ( mSourceCRS != mDestCRS )
      65                 :       1523 :     calculateTransforms( context );
      66                 :       1533 : }
      67                 :            : Q_NOWARN_DEPRECATED_POP
      68                 :            : 
      69                 :            : Q_NOWARN_DEPRECATED_PUSH // because of deprecated members
      70                 :          0 : QgsCoordinateTransformPrivate::QgsCoordinateTransformPrivate( const QgsCoordinateReferenceSystem &source, const QgsCoordinateReferenceSystem &destination, int sourceDatumTransform, int destDatumTransform )
      71                 :          0 :   : mSourceCRS( source )
      72                 :          0 :   , mDestCRS( destination )
      73                 :       1851 :   , mSourceDatumTransform( sourceDatumTransform )
      74                 :          0 :   , mDestinationDatumTransform( destDatumTransform )
      75                 :          0 : {
      76                 :          0 : }
      77                 :            : 
      78                 :          0 : QgsCoordinateTransformPrivate::QgsCoordinateTransformPrivate( const QgsCoordinateTransformPrivate &other )
      79                 :          0 :   : QSharedData( other )
      80                 :          0 :   , mAvailableOpCount( other.mAvailableOpCount )
      81                 :          0 :   , mIsValid( other.mIsValid )
      82                 :          0 :   , mShortCircuit( other.mShortCircuit )
      83                 :       1851 :   , mSourceCRS( other.mSourceCRS )
      84                 :          0 :   , mDestCRS( other.mDestCRS )
      85                 :          0 :   , mSourceDatumTransform( other.mSourceDatumTransform )
      86                 :          0 :   , mDestinationDatumTransform( other.mDestinationDatumTransform )
      87                 :          0 :   , mProjCoordinateOperation( other.mProjCoordinateOperation )
      88                 :          0 :   , mShouldReverseCoordinateOperation( other.mShouldReverseCoordinateOperation )
      89                 :       1851 :   , mAllowFallbackTransforms( other.mAllowFallbackTransforms )
      90                 :          0 :   , mIsReversed( other.mIsReversed )
      91                 :          0 :   , mProjLock()
      92                 :          0 :   , mProjProjections()
      93                 :          0 :   , mProjFallbackProjections()
      94                 :          0 : {
      95                 :          0 : }
      96                 :            : Q_NOWARN_DEPRECATED_POP
      97                 :            : 
      98                 :            : Q_NOWARN_DEPRECATED_PUSH
      99                 :       1851 : QgsCoordinateTransformPrivate::~QgsCoordinateTransformPrivate()
     100                 :       1851 : {
     101                 :       1851 :   // free the proj objects
     102                 :       1851 :   freeProj();
     103                 :       3702 : }
     104                 :       1851 : Q_NOWARN_DEPRECATED_POP
     105                 :            : 
     106                 :       1533 : bool QgsCoordinateTransformPrivate::checkValidity()
     107                 :       1851 : {
     108                 :       1533 :   if ( !mSourceCRS.isValid() || !mDestCRS.isValid() )
     109                 :            :   {
     110                 :       1521 :     invalidate();
     111                 :       1521 :     return false;
     112                 :            :   }
     113                 :         12 :   return true;
     114                 :       1533 : }
     115                 :            : 
     116                 :       1523 : void QgsCoordinateTransformPrivate::invalidate()
     117                 :            : {
     118                 :       1523 :   mShortCircuit = true;
     119                 :       1523 :   mIsValid = false;
     120                 :       1523 :   mAvailableOpCount = -1;
     121                 :       1523 : }
     122                 :            : 
     123                 :          2 : bool QgsCoordinateTransformPrivate::initialize()
     124                 :            : {
     125                 :          2 :   invalidate();
     126                 :          2 :   if ( !mSourceCRS.isValid() )
     127                 :            :   {
     128                 :            :     // Pass through with no projection since we have no idea what the layer
     129                 :            :     // coordinates are and projecting them may not be appropriate
     130                 :          0 :     QgsDebugMsgLevel( QStringLiteral( "Source CRS is invalid!" ), 4 );
     131                 :          0 :     return false;
     132                 :            :   }
     133                 :            : 
     134                 :          2 :   if ( !mDestCRS.isValid() )
     135                 :            :   {
     136                 :            :     //No destination projection is set so we set the default output projection to
     137                 :            :     //be the same as input proj.
     138                 :          0 :     mDestCRS = mSourceCRS;
     139                 :          0 :     QgsDebugMsgLevel( QStringLiteral( "Destination CRS is invalid!" ), 4 );
     140                 :          0 :     return false;
     141                 :            :   }
     142                 :            : 
     143                 :          2 :   mIsValid = true;
     144                 :            : 
     145                 :          2 :   if ( mSourceCRS == mDestCRS )
     146                 :            :   {
     147                 :            :     // If the source and destination projection are the same, set the short
     148                 :            :     // circuit flag (no transform takes place)
     149                 :          1 :     mShortCircuit = true;
     150                 :          1 :     return true;
     151                 :            :   }
     152                 :            : 
     153                 :            :   // init the projections (destination and source)
     154                 :          1 :   freeProj();
     155                 :            : 
     156                 :            :   // create proj projections for current thread
     157                 :          1 :   ProjData res = threadLocalProjData();
     158                 :            : 
     159                 :            : #ifdef COORDINATE_TRANSFORM_VERBOSE
     160                 :            :   QgsDebugMsg( "From proj : " + mSourceCRS.toProj() );
     161                 :            :   QgsDebugMsg( "To proj   : " + mDestCRS.toProj() );
     162                 :            : #endif
     163                 :            : 
     164                 :          1 :   if ( !res )
     165                 :          0 :     mIsValid = false;
     166                 :            : 
     167                 :            : #ifdef COORDINATE_TRANSFORM_VERBOSE
     168                 :            :   if ( mIsValid )
     169                 :            :   {
     170                 :            :     QgsDebugMsg( QStringLiteral( "------------------------------------------------------------" ) );
     171                 :            :     QgsDebugMsg( QStringLiteral( "The OGR Coordinate transformation for this layer was set to" ) );
     172                 :            :     QgsLogger::debug<QgsCoordinateReferenceSystem>( "Input", mSourceCRS, __FILE__, __FUNCTION__, __LINE__ );
     173                 :            :     QgsLogger::debug<QgsCoordinateReferenceSystem>( "Output", mDestCRS, __FILE__, __FUNCTION__, __LINE__ );
     174                 :            :     QgsDebugMsg( QStringLiteral( "------------------------------------------------------------" ) );
     175                 :            :   }
     176                 :            :   else
     177                 :            :   {
     178                 :            :     QgsDebugMsg( QStringLiteral( "------------------------------------------------------------" ) );
     179                 :            :     QgsDebugMsg( QStringLiteral( "The OGR Coordinate transformation FAILED TO INITIALIZE!" ) );
     180                 :            :     QgsDebugMsg( QStringLiteral( "------------------------------------------------------------" ) );
     181                 :            :   }
     182                 :            : #else
     183                 :          1 :   if ( !mIsValid )
     184                 :            :   {
     185                 :          0 :     QgsDebugMsg( QStringLiteral( "Coordinate transformation failed to initialize!" ) );
     186                 :          0 :   }
     187                 :            : #endif
     188                 :            : 
     189                 :            :   // Transform must take place
     190                 :          1 :   mShortCircuit = false;
     191                 :            : 
     192                 :          1 :   return mIsValid;
     193                 :          2 : }
     194                 :            : 
     195                 :       1523 : void QgsCoordinateTransformPrivate::calculateTransforms( const QgsCoordinateTransformContext &context )
     196                 :            : {
     197                 :            :   // recalculate datum transforms from context
     198                 :       1523 :   if ( mSourceCRS.isValid() && mDestCRS.isValid() )
     199                 :            :   {
     200                 :          6 :     mProjCoordinateOperation = context.calculateCoordinateOperation( mSourceCRS, mDestCRS );
     201                 :          6 :     mShouldReverseCoordinateOperation = context.mustReverseCoordinateOperation( mSourceCRS, mDestCRS );
     202                 :          6 :     mAllowFallbackTransforms = context.allowFallbackTransform( mSourceCRS, mDestCRS );
     203                 :          6 :   }
     204                 :            :   else
     205                 :            :   {
     206                 :       1517 :     mProjCoordinateOperation.clear();
     207                 :       1517 :     mShouldReverseCoordinateOperation = false;
     208                 :       1517 :     mAllowFallbackTransforms = false;
     209                 :            :   }
     210                 :       1523 : }
     211                 :            : 
     212                 :          0 : static void proj_collecting_logger( void *user_data, int /*level*/, const char *message )
     213                 :            : {
     214                 :          0 :   QStringList *dest = reinterpret_cast< QStringList * >( user_data );
     215                 :          0 :   dest->append( QString( message ) );
     216                 :          0 : }
     217                 :            : 
     218                 :          0 : static void proj_logger( void *, int level, const char *message )
     219                 :            : {
     220                 :            : #ifndef QGISDEBUG
     221                 :            :   Q_UNUSED( message )
     222                 :            : #endif
     223                 :          0 :   if ( level == PJ_LOG_ERROR )
     224                 :            :   {
     225                 :          0 :     QgsDebugMsg( QString( message ) );
     226                 :          0 :   }
     227                 :          0 :   else if ( level == PJ_LOG_DEBUG )
     228                 :            :   {
     229                 :          0 :     QgsDebugMsgLevel( QString( message ), 3 );
     230                 :          0 :   }
     231                 :          0 : }
     232                 :            : 
     233                 :         27 : ProjData QgsCoordinateTransformPrivate::threadLocalProjData()
     234                 :            : {
     235                 :         27 :   QgsReadWriteLocker locker( mProjLock, QgsReadWriteLocker::Read );
     236                 :            : 
     237                 :         27 :   PJ_CONTEXT *context = QgsProjContext::get();
     238                 :         27 :   QMap < uintptr_t, ProjData >::const_iterator it = mProjProjections.constFind( reinterpret_cast< uintptr_t>( context ) );
     239                 :            : 
     240                 :         27 :   if ( it != mProjProjections.constEnd() )
     241                 :            :   {
     242                 :         26 :     ProjData res = it.value();
     243                 :         26 :     return res;
     244                 :            :   }
     245                 :            : 
     246                 :            :   // proj projections don't exist yet, so we need to create
     247                 :          1 :   locker.changeMode( QgsReadWriteLocker::Write );
     248                 :            : 
     249                 :            :   // use a temporary proj error collector
     250                 :          1 :   QStringList projErrors;
     251                 :          1 :   proj_log_func( context, &projErrors, proj_collecting_logger );
     252                 :            : 
     253                 :          1 :   mIsReversed = false;
     254                 :            : 
     255                 :          1 :   QgsProjUtils::proj_pj_unique_ptr transform;
     256                 :          1 :   if ( !mProjCoordinateOperation.isEmpty() )
     257                 :            :   {
     258                 :          0 :     transform.reset( proj_create( context, mProjCoordinateOperation.toUtf8().constData() ) );
     259                 :          0 :     if ( !transform || !proj_coordoperation_is_instantiable( context, transform.get() ) )
     260                 :            :     {
     261                 :          0 :       if ( sMissingGridUsedByContextHandler )
     262                 :            :       {
     263                 :          0 :         QgsDatumTransform::TransformDetails desired;
     264                 :          0 :         desired.proj = mProjCoordinateOperation;
     265                 :          0 :         desired.accuracy = -1; //unknown, can't retrieve from proj as we can't instantiate the op
     266                 :          0 :         desired.grids = QgsProjUtils::gridsUsed( mProjCoordinateOperation );
     267                 :          0 :         sMissingGridUsedByContextHandler( mSourceCRS, mDestCRS, desired );
     268                 :          0 :       }
     269                 :            :       else
     270                 :            :       {
     271                 :          0 :         const QString err = QObject::tr( "Could not use operation specified in project between %1 and %2. (Wanted to use: %3)." ).arg( mSourceCRS.authid(),
     272                 :          0 :                             mDestCRS.authid(),
     273                 :          0 :                             mProjCoordinateOperation );
     274                 :          0 :         QgsMessageLog::logMessage( err, QString(), Qgis::Critical );
     275                 :          0 :       }
     276                 :            : 
     277                 :          0 :       transform.reset();
     278                 :          0 :     }
     279                 :            :     else
     280                 :            :     {
     281                 :          0 :       mIsReversed = mShouldReverseCoordinateOperation;
     282                 :            :     }
     283                 :          0 :   }
     284                 :            : 
     285                 :          1 :   QString nonAvailableError;
     286                 :          1 :   if ( !transform ) // fallback on default proj pathway
     287                 :            :   {
     288                 :          1 :     if ( !mSourceCRS.projObject() || ! mDestCRS.projObject() )
     289                 :            :     {
     290                 :          0 :       proj_log_func( context, nullptr, nullptr );
     291                 :          0 :       return nullptr;
     292                 :            :     }
     293                 :            : 
     294                 :          1 :     PJ_OPERATION_FACTORY_CONTEXT *operationContext = proj_create_operation_factory_context( context, nullptr );
     295                 :            : 
     296                 :            :     // We want to check ALL grids, not just those available for use
     297                 :          1 :     proj_operation_factory_context_set_grid_availability_use( context, operationContext, PROJ_GRID_AVAILABILITY_IGNORED );
     298                 :            : 
     299                 :            :     // See https://lists.osgeo.org/pipermail/proj/2019-May/008604.html
     300                 :          1 :     proj_operation_factory_context_set_spatial_criterion( context, operationContext, PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION );
     301                 :            : 
     302                 :          1 :     if ( PJ_OBJ_LIST *ops = proj_create_operations( context, mSourceCRS.projObject(), mDestCRS.projObject(), operationContext ) )
     303                 :            :     {
     304                 :          1 :       mAvailableOpCount = proj_list_get_count( ops );
     305                 :          1 :       if ( mAvailableOpCount < 1 )
     306                 :            :       {
     307                 :            :         // huh?
     308                 :          0 :         int errNo = proj_context_errno( context );
     309                 :          0 :         if ( errNo && errNo != -61 )
     310                 :            :         {
     311                 :          0 :           nonAvailableError = QString( proj_errno_string( errNo ) );
     312                 :          0 :         }
     313                 :            :         else
     314                 :            :         {
     315                 :          0 :           nonAvailableError = QObject::tr( "No coordinate operations are available between these two reference systems" );
     316                 :            :         }
     317                 :          0 :       }
     318                 :          1 :       else if ( mAvailableOpCount == 1 )
     319                 :            :       {
     320                 :            :         // only a single operation available. Can we use it?
     321                 :          0 :         transform.reset( proj_list_get( context, ops, 0 ) );
     322                 :          0 :         if ( transform )
     323                 :            :         {
     324                 :          0 :           if ( !proj_coordoperation_is_instantiable( context, transform.get() ) )
     325                 :            :           {
     326                 :            :             // uh oh :( something is missing! find what it is
     327                 :          0 :             for ( int j = 0; j < proj_coordoperation_get_grid_used_count( context, transform.get() ); ++j )
     328                 :            :             {
     329                 :          0 :               const char *shortName = nullptr;
     330                 :          0 :               const char *fullName = nullptr;
     331                 :          0 :               const char *packageName = nullptr;
     332                 :          0 :               const char *url = nullptr;
     333                 :          0 :               int directDownload = 0;
     334                 :          0 :               int openLicense = 0;
     335                 :          0 :               int isAvailable = 0;
     336                 :          0 :               proj_coordoperation_get_grid_used( context, transform.get(), j, &shortName, &fullName, &packageName, &url, &directDownload, &openLicense, &isAvailable );
     337                 :          0 :               if ( !isAvailable )
     338                 :            :               {
     339                 :            :                 // found it!
     340                 :          0 :                 if ( sMissingRequiredGridHandler )
     341                 :            :                 {
     342                 :          0 :                   QgsDatumTransform::GridDetails gridDetails;
     343                 :          0 :                   gridDetails.shortName = QString( shortName );
     344                 :          0 :                   gridDetails.fullName = QString( fullName );
     345                 :          0 :                   gridDetails.packageName = QString( packageName );
     346                 :          0 :                   gridDetails.url = QString( url );
     347                 :          0 :                   gridDetails.directDownload = directDownload;
     348                 :          0 :                   gridDetails.openLicense = openLicense;
     349                 :          0 :                   gridDetails.isAvailable = isAvailable;
     350                 :          0 :                   sMissingRequiredGridHandler( mSourceCRS, mDestCRS, gridDetails );
     351                 :          0 :                 }
     352                 :            :                 else
     353                 :            :                 {
     354                 :          0 :                   const QString err = QObject::tr( "Cannot create transform between %1 and %2, missing required grid %3" ).arg( mSourceCRS.authid(),
     355                 :          0 :                                       mDestCRS.authid(),
     356                 :            :                                       shortName );
     357                 :          0 :                   QgsMessageLog::logMessage( err, QString(), Qgis::Critical );
     358                 :          0 :                 }
     359                 :          0 :                 break;
     360                 :            :               }
     361                 :          0 :             }
     362                 :          0 :           }
     363                 :            :           else
     364                 :            :           {
     365                 :            : 
     366                 :            :             // transform may have either the source or destination CRS using swapped axis order. For QGIS, we ALWAYS need regular x/y axis order
     367                 :          0 :             transform.reset( proj_normalize_for_visualization( context, transform.get() ) );
     368                 :          0 :             if ( !transform )
     369                 :            :             {
     370                 :          0 :               const QString err = QObject::tr( "Cannot normalize transform between %1 and %2" ).arg( mSourceCRS.authid(),
     371                 :          0 :                                   mDestCRS.authid() );
     372                 :          0 :               QgsMessageLog::logMessage( err, QString(), Qgis::Critical );
     373                 :          0 :             }
     374                 :            :           }
     375                 :          0 :         }
     376                 :          0 :       }
     377                 :            :       else
     378                 :            :       {
     379                 :            :         // multiple operations available. Can we use the best one?
     380                 :          1 :         QgsDatumTransform::TransformDetails preferred;
     381                 :          1 :         bool missingPreferred = false;
     382                 :          1 :         bool stillLookingForPreferred = true;
     383                 :          1 :         for ( int i = 0; i < mAvailableOpCount; ++ i )
     384                 :            :         {
     385                 :          1 :           transform.reset( proj_list_get( context, ops, i ) );
     386                 :          1 :           const bool isInstantiable = transform && proj_coordoperation_is_instantiable( context, transform.get() );
     387                 :          1 :           if ( stillLookingForPreferred && transform && !isInstantiable )
     388                 :            :           {
     389                 :            :             // uh oh :( something is missing blocking us from the preferred operation!
     390                 :          0 :             QgsDatumTransform::TransformDetails candidate = QgsDatumTransform::transformDetailsFromPj( transform.get() );
     391                 :          0 :             if ( !candidate.proj.isEmpty() )
     392                 :            :             {
     393                 :          0 :               preferred = candidate;
     394                 :          0 :               missingPreferred = true;
     395                 :          0 :               stillLookingForPreferred = false;
     396                 :          0 :             }
     397                 :          0 :           }
     398                 :          1 :           if ( transform && isInstantiable )
     399                 :            :           {
     400                 :            :             // found one
     401                 :          1 :             break;
     402                 :            :           }
     403                 :          0 :           transform.reset();
     404                 :          0 :         }
     405                 :            : 
     406                 :          1 :         if ( transform && missingPreferred )
     407                 :            :         {
     408                 :            :           // found a transform, but it's not the preferred
     409                 :          0 :           QgsDatumTransform::TransformDetails available = QgsDatumTransform::transformDetailsFromPj( transform.get() );
     410                 :          0 :           if ( sMissingPreferredGridHandler )
     411                 :            :           {
     412                 :          0 :             sMissingPreferredGridHandler( mSourceCRS, mDestCRS, preferred, available );
     413                 :          0 :           }
     414                 :            :           else
     415                 :            :           {
     416                 :          0 :             const QString err = QObject::tr( "Using non-preferred coordinate operation between %1 and %2. Using %3, preferred %4." ).arg( mSourceCRS.authid(),
     417                 :          0 :                                 mDestCRS.authid(),
     418                 :          0 :                                 available.proj,
     419                 :          0 :                                 preferred.proj );
     420                 :          0 :             QgsMessageLog::logMessage( err, QString(), Qgis::Critical );
     421                 :          0 :           }
     422                 :          0 :         }
     423                 :            : 
     424                 :            :         // transform may have either the source or destination CRS using swapped axis order. For QGIS, we ALWAYS need regular x/y axis order
     425                 :          1 :         if ( transform )
     426                 :          1 :           transform.reset( proj_normalize_for_visualization( context, transform.get() ) );
     427                 :          1 :         if ( !transform )
     428                 :            :         {
     429                 :          0 :           const QString err = QObject::tr( "Cannot normalize transform between %1 and %2" ).arg( mSourceCRS.authid(),
     430                 :          0 :                               mDestCRS.authid() );
     431                 :          0 :           QgsMessageLog::logMessage( err, QString(), Qgis::Critical );
     432                 :          0 :         }
     433                 :          1 :       }
     434                 :          1 :       proj_list_destroy( ops );
     435                 :          1 :     }
     436                 :          1 :     proj_operation_factory_context_destroy( operationContext );
     437                 :          1 :   }
     438                 :            : 
     439                 :          1 :   if ( !transform && nonAvailableError.isEmpty() )
     440                 :            :   {
     441                 :          0 :     int errNo = proj_context_errno( context );
     442                 :          0 :     if ( errNo && errNo != -61 )
     443                 :            :     {
     444                 :          0 :       nonAvailableError = QString( proj_errno_string( errNo ) );
     445                 :          0 :     }
     446                 :          0 :     else if ( !projErrors.empty() )
     447                 :            :     {
     448                 :          0 :       nonAvailableError = projErrors.constLast();
     449                 :          0 :     }
     450                 :            : 
     451                 :          0 :     if ( nonAvailableError.isEmpty() )
     452                 :            :     {
     453                 :          0 :       nonAvailableError = QObject::tr( "No coordinate operations are available between these two reference systems" );
     454                 :          0 :     }
     455                 :            :     else
     456                 :            :     {
     457                 :            :       // strip proj prefixes from error string, so that it's nicer for users
     458                 :          0 :       nonAvailableError = nonAvailableError.remove( QStringLiteral( "internal_proj_create_operations: " ) );
     459                 :            :     }
     460                 :          0 :   }
     461                 :            : 
     462                 :          1 :   if ( !nonAvailableError.isEmpty() )
     463                 :            :   {
     464                 :          0 :     if ( sCoordinateOperationCreationErrorHandler )
     465                 :            :     {
     466                 :          0 :       sCoordinateOperationCreationErrorHandler( mSourceCRS, mDestCRS, nonAvailableError );
     467                 :          0 :     }
     468                 :            :     else
     469                 :            :     {
     470                 :          0 :       const QString err = QObject::tr( "Cannot create transform between %1 and %2: %3" ).arg( mSourceCRS.authid(),
     471                 :          0 :                           mDestCRS.authid(),
     472                 :            :                           nonAvailableError );
     473                 :          0 :       QgsMessageLog::logMessage( err, QString(), Qgis::Critical );
     474                 :          0 :     }
     475                 :          0 :   }
     476                 :            : 
     477                 :            :   // reset logger to terminal output
     478                 :          1 :   proj_log_func( context, nullptr, proj_logger );
     479                 :            : 
     480                 :          1 :   if ( !transform )
     481                 :            :   {
     482                 :            :     // ouch!
     483                 :          0 :     return nullptr;
     484                 :            :   }
     485                 :            : 
     486                 :          1 :   ProjData res = transform.release();
     487                 :          1 :   mProjProjections.insert( reinterpret_cast< uintptr_t>( context ), res );
     488                 :          1 :   return res;
     489                 :         27 : }
     490                 :            : 
     491                 :          0 : ProjData QgsCoordinateTransformPrivate::threadLocalFallbackProjData()
     492                 :            : {
     493                 :          0 :   QgsReadWriteLocker locker( mProjLock, QgsReadWriteLocker::Read );
     494                 :            : 
     495                 :          0 :   PJ_CONTEXT *context = QgsProjContext::get();
     496                 :          0 :   QMap < uintptr_t, ProjData >::const_iterator it = mProjFallbackProjections.constFind( reinterpret_cast< uintptr_t>( context ) );
     497                 :            : 
     498                 :          0 :   if ( it != mProjFallbackProjections.constEnd() )
     499                 :            :   {
     500                 :          0 :     ProjData res = it.value();
     501                 :          0 :     return res;
     502                 :            :   }
     503                 :            : 
     504                 :            :   // proj projections don't exist yet, so we need to create
     505                 :          0 :   locker.changeMode( QgsReadWriteLocker::Write );
     506                 :            : 
     507                 :          0 :   QgsProjUtils::proj_pj_unique_ptr transform( proj_create_crs_to_crs_from_pj( context, mSourceCRS.projObject(), mDestCRS.projObject(), nullptr, nullptr ) );
     508                 :          0 :   if ( transform )
     509                 :          0 :     transform.reset( proj_normalize_for_visualization( QgsProjContext::get(), transform.get() ) );
     510                 :            : 
     511                 :          0 :   ProjData res = transform.release();
     512                 :          0 :   mProjFallbackProjections.insert( reinterpret_cast< uintptr_t>( context ), res );
     513                 :          0 :   return res;
     514                 :          0 : }
     515                 :            : 
     516                 :          0 : void QgsCoordinateTransformPrivate::setCustomMissingRequiredGridHandler( const std::function<void ( const QgsCoordinateReferenceSystem &, const QgsCoordinateReferenceSystem &, const QgsDatumTransform::GridDetails & )> &handler )
     517                 :            : {
     518                 :          0 :   sMissingRequiredGridHandler = handler;
     519                 :          0 : }
     520                 :            : 
     521                 :          0 : void QgsCoordinateTransformPrivate::setCustomMissingPreferredGridHandler( const std::function<void ( const QgsCoordinateReferenceSystem &, const QgsCoordinateReferenceSystem &, const QgsDatumTransform::TransformDetails &, const QgsDatumTransform::TransformDetails & )> &handler )
     522                 :            : {
     523                 :          0 :   sMissingPreferredGridHandler = handler;
     524                 :          0 : }
     525                 :            : 
     526                 :          0 : void QgsCoordinateTransformPrivate::setCustomCoordinateOperationCreationErrorHandler( const std::function<void ( const QgsCoordinateReferenceSystem &, const QgsCoordinateReferenceSystem &, const QString & )> &handler )
     527                 :            : {
     528                 :          0 :   sCoordinateOperationCreationErrorHandler = handler;
     529                 :          0 : }
     530                 :            : 
     531                 :          0 : void QgsCoordinateTransformPrivate::setCustomMissingGridUsedByContextHandler( const std::function<void ( const QgsCoordinateReferenceSystem &, const QgsCoordinateReferenceSystem &, const QgsDatumTransform::TransformDetails & )> &handler )
     532                 :            : {
     533                 :          0 :   sMissingGridUsedByContextHandler = handler;
     534                 :          0 : }
     535                 :            : 
     536                 :       1852 : void QgsCoordinateTransformPrivate::freeProj()
     537                 :            : {
     538                 :       1852 :   QgsReadWriteLocker locker( mProjLock, QgsReadWriteLocker::Write );
     539                 :       1852 :   if ( mProjProjections.isEmpty() && mProjFallbackProjections.isEmpty() )
     540                 :       1851 :     return;
     541                 :          1 :   QMap < uintptr_t, ProjData >::const_iterator it = mProjProjections.constBegin();
     542                 :            : 
     543                 :            :   // During destruction of PJ* objects, the errno is set in the underlying
     544                 :            :   // context. Consequently the context attached to the PJ* must still exist !
     545                 :            :   // Which is not necessarily the case currently unfortunately. So
     546                 :            :   // create a temporary dummy context, and attach it to the PJ* before destroying
     547                 :            :   // it
     548                 :          1 :   PJ_CONTEXT *tmpContext = proj_context_create();
     549                 :          2 :   for ( ; it != mProjProjections.constEnd(); ++it )
     550                 :            :   {
     551                 :          1 :     proj_assign_context( it.value(), tmpContext );
     552                 :          1 :     proj_destroy( it.value() );
     553                 :          1 :   }
     554                 :            : 
     555                 :          1 :   it = mProjFallbackProjections.constBegin();
     556                 :          1 :   for ( ; it != mProjFallbackProjections.constEnd(); ++it )
     557                 :            :   {
     558                 :          0 :     proj_assign_context( it.value(), tmpContext );
     559                 :          0 :     proj_destroy( it.value() );
     560                 :          0 :   }
     561                 :            : 
     562                 :          1 :   proj_context_destroy( tmpContext );
     563                 :          1 :   mProjProjections.clear();
     564                 :          1 :   mProjFallbackProjections.clear();
     565                 :       1852 : }
     566                 :            : 
     567                 :          0 : bool QgsCoordinateTransformPrivate::removeObjectsBelongingToCurrentThread( void *pj_context )
     568                 :            : {
     569                 :          0 :   QgsReadWriteLocker locker( mProjLock, QgsReadWriteLocker::Write );
     570                 :            : 
     571                 :          0 :   QMap < uintptr_t, ProjData >::iterator it = mProjProjections.find( reinterpret_cast< uintptr_t>( pj_context ) );
     572                 :          0 :   if ( it != mProjProjections.end() )
     573                 :            :   {
     574                 :          0 :     proj_destroy( it.value() );
     575                 :          0 :     mProjProjections.erase( it );
     576                 :          0 :   }
     577                 :            : 
     578                 :          0 :   it = mProjFallbackProjections.find( reinterpret_cast< uintptr_t>( pj_context ) );
     579                 :          0 :   if ( it != mProjFallbackProjections.end() )
     580                 :            :   {
     581                 :          0 :     proj_destroy( it.value() );
     582                 :          0 :     mProjFallbackProjections.erase( it );
     583                 :          0 :   }
     584                 :            : 
     585                 :          0 :   return mProjProjections.isEmpty();
     586                 :          0 : }
     587                 :            : 
     588                 :            : ///@endcond

Generated by: LCOV version 1.14