LCOV - code coverage report
Current view: top level - core - qgscoordinatereferencesystem.cpp (source / functions) Hit Total Coverage
Test: coverage.info.cleaned Lines: 393 1507 26.1 %
Date: 2021-03-26 12:19:53 Functions: 0 0 -
Branches: 0 0 -

           Branch data     Line data    Source code
       1                 :            : /***************************************************************************
       2                 :            :                           qgscoordinatereferencesystem.cpp
       3                 :            : 
       4                 :            :                              -------------------
       5                 :            :     begin                : 2007
       6                 :            :     copyright            : (C) 2007 by Gary E. Sherman
       7                 :            :     email                : sherman@mrcc.com
       8                 :            : ***************************************************************************/
       9                 :            : 
      10                 :            : /***************************************************************************
      11                 :            :  *                                                                         *
      12                 :            :  *   This program is free software; you can redistribute it and/or modify  *
      13                 :            :  *   it under the terms of the GNU General Public License as published by  *
      14                 :            :  *   the Free Software Foundation; either version 2 of the License, or     *
      15                 :            :  *   (at your option) any later version.                                   *
      16                 :            :  *                                                                         *
      17                 :            :  ***************************************************************************/
      18                 :            : #include "qgscoordinatereferencesystem.h"
      19                 :            : #include "qgscoordinatereferencesystem_p.h"
      20                 :            : 
      21                 :            : #include "qgscoordinatereferencesystem_legacy.h"
      22                 :            : #include "qgscoordinatereferencesystemregistry.h"
      23                 :            : #include "qgsreadwritelocker.h"
      24                 :            : 
      25                 :            : #include <cmath>
      26                 :            : 
      27                 :            : #include <QDir>
      28                 :            : #include <QDomNode>
      29                 :            : #include <QDomElement>
      30                 :            : #include <QFileInfo>
      31                 :            : #include <QRegExp>
      32                 :            : #include <QTextStream>
      33                 :            : #include <QFile>
      34                 :            : #include <QRegularExpression>
      35                 :            : 
      36                 :            : #include "qgsapplication.h"
      37                 :            : #include "qgslogger.h"
      38                 :            : #include "qgsmessagelog.h"
      39                 :            : #include "qgis.h" //const vals declared here
      40                 :            : #include "qgslocalec.h"
      41                 :            : #include "qgssettings.h"
      42                 :            : #include "qgsogrutils.h"
      43                 :            : 
      44                 :            : #include <sqlite3.h>
      45                 :            : #include "qgsprojutils.h"
      46                 :            : #include <proj.h>
      47                 :            : #include <proj_experimental.h>
      48                 :            : 
      49                 :            : //gdal and ogr includes (needed for == operator)
      50                 :            : #include <ogr_srs_api.h>
      51                 :            : #include <cpl_error.h>
      52                 :            : #include <cpl_conv.h>
      53                 :            : #include <cpl_csv.h>
      54                 :            : 
      55                 :            : CUSTOM_CRS_VALIDATION QgsCoordinateReferenceSystem::sCustomSrsValidation = nullptr;
      56                 :            : 
      57                 :            : typedef QHash< long, QgsCoordinateReferenceSystem > SrIdCrsCacheHash;
      58                 :            : typedef QHash< QString, QgsCoordinateReferenceSystem > StringCrsCacheHash;
      59                 :            : 
      60                 :         31 : Q_GLOBAL_STATIC( QReadWriteLock, sSrIdCacheLock )
      61                 :         20 : Q_GLOBAL_STATIC( SrIdCrsCacheHash, sSrIdCache )
      62                 :            : bool QgsCoordinateReferenceSystem::sDisableSrIdCache = false;
      63                 :            : 
      64                 :         45 : Q_GLOBAL_STATIC( QReadWriteLock, sOgcLock )
      65                 :         57 : Q_GLOBAL_STATIC( StringCrsCacheHash, sOgcCache )
      66                 :            : bool QgsCoordinateReferenceSystem::sDisableOgcCache = false;
      67                 :            : 
      68                 :         32 : Q_GLOBAL_STATIC( QReadWriteLock, sProj4CacheLock )
      69                 :         20 : Q_GLOBAL_STATIC( StringCrsCacheHash, sProj4Cache )
      70                 :            : bool QgsCoordinateReferenceSystem::sDisableProjCache = false;
      71                 :            : 
      72                 :         34 : Q_GLOBAL_STATIC( QReadWriteLock, sCRSWktLock )
      73                 :         28 : Q_GLOBAL_STATIC( StringCrsCacheHash, sWktCache )
      74                 :            : bool QgsCoordinateReferenceSystem::sDisableWktCache = false;
      75                 :            : 
      76                 :         31 : Q_GLOBAL_STATIC( QReadWriteLock, sCRSSrsIdLock )
      77                 :         20 : Q_GLOBAL_STATIC( SrIdCrsCacheHash, sSrsIdCache )
      78                 :            : bool QgsCoordinateReferenceSystem::sDisableSrsIdCache = false;
      79                 :            : 
      80                 :         85 : Q_GLOBAL_STATIC( QReadWriteLock, sCrsStringLock )
      81                 :        136 : Q_GLOBAL_STATIC( StringCrsCacheHash, sStringCache )
      82                 :            : bool QgsCoordinateReferenceSystem::sDisableStringCache = false;
      83                 :            : 
      84                 :         10 : QString getFullProjString( PJ *obj )
      85                 :            : {
      86                 :            :   // see https://lists.osgeo.org/pipermail/proj/2019-May/008565.html, it's not sufficient to just
      87                 :            :   // use proj_as_proj_string
      88                 :         10 :   QgsProjUtils::proj_pj_unique_ptr boundCrs( proj_crs_create_bound_crs_to_WGS84( QgsProjContext::get(), obj, nullptr ) );
      89                 :         10 :   if ( boundCrs )
      90                 :            :   {
      91                 :         10 :     if ( const char *proj4src = proj_as_proj_string( QgsProjContext::get(), boundCrs.get(), PJ_PROJ_4, nullptr ) )
      92                 :            :     {
      93                 :         10 :       return QString( proj4src );
      94                 :            :     }
      95                 :          0 :   }
      96                 :            : 
      97                 :          0 :   return QString( proj_as_proj_string( QgsProjContext::get(), obj, PJ_PROJ_4, nullptr ) );
      98                 :         10 : }
      99                 :            : //--------------------------
     100                 :            : 
     101                 :       2262 : QgsCoordinateReferenceSystem::QgsCoordinateReferenceSystem()
     102                 :            : {
     103                 :       2262 :   static QgsCoordinateReferenceSystem nullCrs = QgsCoordinateReferenceSystem( QString() );
     104                 :            : 
     105                 :       2262 :   d = nullCrs.d;
     106                 :       2262 : }
     107                 :            : 
     108                 :         58 : QgsCoordinateReferenceSystem::QgsCoordinateReferenceSystem( const QString &definition )
     109                 :            : {
     110                 :         58 :   d = new QgsCoordinateReferenceSystemPrivate();
     111                 :         58 :   createFromString( definition );
     112                 :         58 : }
     113                 :            : 
     114                 :          0 : QgsCoordinateReferenceSystem::QgsCoordinateReferenceSystem( const long id, CrsType type )
     115                 :            : {
     116                 :          0 :   d = new QgsCoordinateReferenceSystemPrivate();
     117                 :            :   Q_NOWARN_DEPRECATED_PUSH
     118                 :          0 :   createFromId( id, type );
     119                 :            :   Q_NOWARN_DEPRECATED_POP
     120                 :          0 : }
     121                 :            : 
     122                 :       6166 : QgsCoordinateReferenceSystem::QgsCoordinateReferenceSystem( const QgsCoordinateReferenceSystem &srs )  //NOLINT
     123                 :       6166 :   : d( srs.d )
     124                 :       6166 :   , mValidationHint( srs.mValidationHint )
     125                 :            : {
     126                 :       6166 : }
     127                 :            : 
     128                 :        978 : QgsCoordinateReferenceSystem &QgsCoordinateReferenceSystem::operator=( const QgsCoordinateReferenceSystem &srs )  //NOLINT
     129                 :            : {
     130                 :        978 :   d = srs.d;
     131                 :        978 :   mValidationHint = srs.mValidationHint;
     132                 :        978 :   return *this;
     133                 :            : }
     134                 :            : 
     135                 :          0 : QList<long> QgsCoordinateReferenceSystem::validSrsIds()
     136                 :            : {
     137                 :          0 :   QList<long> results;
     138                 :            :   // check both standard & user defined projection databases
     139                 :          0 :   QStringList dbs = QStringList() <<  QgsApplication::srsDatabaseFilePath() << QgsApplication::qgisUserDatabaseFilePath();
     140                 :            : 
     141                 :          0 :   const auto constDbs = dbs;
     142                 :          0 :   for ( const QString &db : constDbs )
     143                 :            :   {
     144                 :          0 :     QFileInfo myInfo( db );
     145                 :          0 :     if ( !myInfo.exists() )
     146                 :            :     {
     147                 :          0 :       QgsDebugMsg( "failed : " + db + " does not exist!" );
     148                 :          0 :       continue;
     149                 :            :     }
     150                 :            : 
     151                 :          0 :     sqlite3_database_unique_ptr database;
     152                 :          0 :     sqlite3_statement_unique_ptr statement;
     153                 :            : 
     154                 :            :     //check the db is available
     155                 :          0 :     int result = openDatabase( db, database );
     156                 :          0 :     if ( result != SQLITE_OK )
     157                 :            :     {
     158                 :          0 :       QgsDebugMsg( "failed : " + db + " could not be opened!" );
     159                 :          0 :       continue;
     160                 :            :     }
     161                 :            : 
     162                 :          0 :     QString sql = QStringLiteral( "select srs_id from tbl_srs" );
     163                 :            :     int rc;
     164                 :          0 :     statement = database.prepare( sql, rc );
     165                 :          0 :     while ( true )
     166                 :            :     {
     167                 :            :       // this one is an infinitive loop, intended to fetch any row
     168                 :          0 :       int ret = statement.step();
     169                 :            : 
     170                 :          0 :       if ( ret == SQLITE_DONE )
     171                 :            :       {
     172                 :            :         // there are no more rows to fetch - we can stop looping
     173                 :          0 :         break;
     174                 :            :       }
     175                 :            : 
     176                 :          0 :       if ( ret == SQLITE_ROW )
     177                 :            :       {
     178                 :          0 :         results.append( statement.columnAsInt64( 0 ) );
     179                 :          0 :       }
     180                 :            :       else
     181                 :            :       {
     182                 :          0 :         QgsMessageLog::logMessage( QObject::tr( "SQLite error: %2\nSQL: %1" ).arg( sql, sqlite3_errmsg( database.get() ) ), QObject::tr( "SpatiaLite" ) );
     183                 :          0 :         break;
     184                 :            :       }
     185                 :            :     }
     186                 :          0 :   }
     187                 :          0 :   std::sort( results.begin(), results.end() );
     188                 :          0 :   return results;
     189                 :          0 : }
     190                 :            : 
     191                 :          6 : QgsCoordinateReferenceSystem QgsCoordinateReferenceSystem::fromOgcWmsCrs( const QString &ogcCrs )
     192                 :            : {
     193                 :          6 :   QgsCoordinateReferenceSystem crs;
     194                 :          6 :   crs.createFromOgcWmsCrs( ogcCrs );
     195                 :          6 :   return crs;
     196                 :          6 : }
     197                 :            : 
     198                 :          2 : QgsCoordinateReferenceSystem QgsCoordinateReferenceSystem::fromEpsgId( long epsg )
     199                 :            : {
     200                 :          2 :   QgsCoordinateReferenceSystem res = fromOgcWmsCrs( "EPSG:" + QString::number( epsg ) );
     201                 :          2 :   if ( res.isValid() )
     202                 :          2 :     return res;
     203                 :            : 
     204                 :            :   // pre proj6 builds allowed use of ESRI: codes here (e.g. 54030), so we need to keep compatibility
     205                 :          0 :   res = fromOgcWmsCrs( "ESRI:" + QString::number( epsg ) );
     206                 :          0 :   if ( res.isValid() )
     207                 :          0 :     return res;
     208                 :            : 
     209                 :          0 :   return QgsCoordinateReferenceSystem();
     210                 :          2 : }
     211                 :            : 
     212                 :          0 : QgsCoordinateReferenceSystem QgsCoordinateReferenceSystem::fromProj4( const QString &proj4 )
     213                 :            : {
     214                 :          0 :   return fromProj( proj4 );
     215                 :            : }
     216                 :            : 
     217                 :          0 : QgsCoordinateReferenceSystem QgsCoordinateReferenceSystem::fromProj( const QString &proj )
     218                 :            : {
     219                 :          0 :   QgsCoordinateReferenceSystem crs;
     220                 :          0 :   crs.createFromProj( proj );
     221                 :          0 :   return crs;
     222                 :          0 : }
     223                 :            : 
     224                 :          3 : QgsCoordinateReferenceSystem QgsCoordinateReferenceSystem::fromWkt( const QString &wkt )
     225                 :            : {
     226                 :          3 :   QgsCoordinateReferenceSystem crs;
     227                 :          3 :   crs.createFromWkt( wkt );
     228                 :          3 :   return crs;
     229                 :          3 : }
     230                 :            : 
     231                 :          0 : QgsCoordinateReferenceSystem QgsCoordinateReferenceSystem::fromSrsId( long srsId )
     232                 :            : {
     233                 :          0 :   QgsCoordinateReferenceSystem crs;
     234                 :          0 :   crs.createFromSrsId( srsId );
     235                 :          0 :   return crs;
     236                 :          0 : }
     237                 :            : 
     238                 :       8418 : QgsCoordinateReferenceSystem::~QgsCoordinateReferenceSystem() //NOLINT
     239                 :            : {
     240                 :       8418 : }
     241                 :            : 
     242                 :          0 : bool QgsCoordinateReferenceSystem::createFromId( const long id, CrsType type )
     243                 :            : {
     244                 :          0 :   bool result = false;
     245                 :          0 :   switch ( type )
     246                 :            :   {
     247                 :            :     case InternalCrsId:
     248                 :          0 :       result = createFromSrsId( id );
     249                 :          0 :       break;
     250                 :            :     case PostgisCrsId:
     251                 :          0 :       result = createFromPostgisSrid( id );
     252                 :          0 :       break;
     253                 :            :     case EpsgCrsId:
     254                 :          0 :       result = createFromOgcWmsCrs( QStringLiteral( "EPSG:%1" ).arg( id ) );
     255                 :          0 :       break;
     256                 :            :     default:
     257                 :            :       //THIS IS BAD...THIS PART OF CODE SHOULD NEVER BE REACHED...
     258                 :          0 :       QgsDebugMsg( QStringLiteral( "Unexpected case reached!" ) );
     259                 :          0 :   };
     260                 :          0 :   return result;
     261                 :          0 : }
     262                 :            : 
     263                 :         59 : bool QgsCoordinateReferenceSystem::createFromString( const QString &definition )
     264                 :            : {
     265                 :         59 :   if ( definition.isEmpty() )
     266                 :          5 :     return false;
     267                 :            : 
     268                 :         54 :   QgsReadWriteLocker locker( *sCrsStringLock(), QgsReadWriteLocker::Read );
     269                 :         54 :   if ( !sDisableStringCache )
     270                 :            :   {
     271                 :         54 :     QHash< QString, QgsCoordinateReferenceSystem >::const_iterator crsIt = sStringCache()->constFind( definition );
     272                 :         54 :     if ( crsIt != sStringCache()->constEnd() )
     273                 :            :     {
     274                 :            :       // found a match in the cache
     275                 :         46 :       *this = crsIt.value();
     276                 :         46 :       return d->mIsValid;
     277                 :            :     }
     278                 :          8 :   }
     279                 :          8 :   locker.unlock();
     280                 :            : 
     281                 :          8 :   bool result = false;
     282                 :         11 :   const thread_local QRegularExpression reCrsId( QStringLiteral( "^(epsg|esri|osgeo|ignf|zangi|iau2000|postgis|internal|user)\\:(\\w+)$" ), QRegularExpression::CaseInsensitiveOption );
     283                 :          8 :   QRegularExpressionMatch match = reCrsId.match( definition );
     284                 :          8 :   if ( match.capturedStart() == 0 )
     285                 :            :   {
     286                 :          8 :     QString authName = match.captured( 1 ).toLower();
     287                 :          8 :     if ( authName == QLatin1String( "epsg" ) )
     288                 :            :     {
     289                 :          8 :       result = createFromOgcWmsCrs( definition );
     290                 :          8 :     }
     291                 :          0 :     else if ( authName == QLatin1String( "postgis" ) )
     292                 :            :     {
     293                 :          0 :       const long id = match.captured( 2 ).toLong();
     294                 :          0 :       result = createFromPostgisSrid( id );
     295                 :          0 :     }
     296                 :          0 :     else if ( authName == QLatin1String( "esri" ) || authName == QLatin1String( "osgeo" ) || authName == QLatin1String( "ignf" ) || authName == QLatin1String( "zangi" ) || authName == QLatin1String( "iau2000" ) )
     297                 :            :     {
     298                 :          0 :       result = createFromOgcWmsCrs( definition );
     299                 :          0 :     }
     300                 :            :     else
     301                 :            :     {
     302                 :          0 :       const long id = match.captured( 2 ).toLong();
     303                 :            :       Q_NOWARN_DEPRECATED_PUSH
     304                 :          0 :       result = createFromId( id, InternalCrsId );
     305                 :            :       Q_NOWARN_DEPRECATED_POP
     306                 :            :     }
     307                 :          8 :   }
     308                 :            :   else
     309                 :            :   {
     310                 :          0 :     const thread_local QRegularExpression reCrsStr( QStringLiteral( "^(?:(wkt|proj4|proj)\\:)?(.+)$" ), QRegularExpression::CaseInsensitiveOption );
     311                 :          0 :     match = reCrsStr.match( definition );
     312                 :          0 :     if ( match.capturedStart() == 0 )
     313                 :            :     {
     314                 :          0 :       if ( match.captured( 1 ).startsWith( QLatin1String( "proj" ), Qt::CaseInsensitive ) )
     315                 :            :       {
     316                 :          0 :         result = createFromProj( match.captured( 2 ) );
     317                 :          0 :       }
     318                 :            :       else
     319                 :            :       {
     320                 :          0 :         result = createFromWkt( match.captured( 2 ) );
     321                 :            :       }
     322                 :          0 :     }
     323                 :            :   }
     324                 :            : 
     325                 :          8 :   locker.changeMode( QgsReadWriteLocker::Write );
     326                 :          8 :   if ( !sDisableStringCache )
     327                 :          8 :     sStringCache()->insert( definition, *this );
     328                 :          8 :   return result;
     329                 :         59 : }
     330                 :            : 
     331                 :          0 : bool QgsCoordinateReferenceSystem::createFromUserInput( const QString &definition )
     332                 :            : {
     333                 :          0 :   if ( definition.isEmpty() )
     334                 :          0 :     return false;
     335                 :            : 
     336                 :          0 :   QString userWkt;
     337                 :          0 :   OGRSpatialReferenceH crs = OSRNewSpatialReference( nullptr );
     338                 :            : 
     339                 :          0 :   if ( OSRSetFromUserInput( crs, definition.toLocal8Bit().constData() ) == OGRERR_NONE )
     340                 :            :   {
     341                 :          0 :     userWkt = QgsOgrUtils::OGRSpatialReferenceToWkt( crs );
     342                 :          0 :     OSRDestroySpatialReference( crs );
     343                 :          0 :   }
     344                 :            :   //QgsDebugMsg( "definition: " + definition + " wkt = " + wkt );
     345                 :          0 :   return createFromWkt( userWkt );
     346                 :          0 : }
     347                 :            : 
     348                 :          0 : void QgsCoordinateReferenceSystem::setupESRIWktFix()
     349                 :            : {
     350                 :            :   // make sure towgs84 parameter is loaded if gdal >= 1.9
     351                 :            :   // this requires setting GDAL_FIX_ESRI_WKT=GEOGCS (see qgis bug #5598 and gdal bug #4673)
     352                 :          0 :   const char *configOld = CPLGetConfigOption( "GDAL_FIX_ESRI_WKT", "" );
     353                 :          0 :   const char *configNew = "GEOGCS";
     354                 :            :   // only set if it was not set, to let user change the value if needed
     355                 :          0 :   if ( strcmp( configOld, "" ) == 0 )
     356                 :            :   {
     357                 :          0 :     CPLSetConfigOption( "GDAL_FIX_ESRI_WKT", configNew );
     358                 :          0 :     if ( strcmp( configNew, CPLGetConfigOption( "GDAL_FIX_ESRI_WKT", "" ) ) != 0 )
     359                 :          0 :       QgsLogger::warning( QStringLiteral( "GDAL_FIX_ESRI_WKT could not be set to %1 : %2" )
     360                 :          0 :                           .arg( configNew, CPLGetConfigOption( "GDAL_FIX_ESRI_WKT", "" ) ) );
     361                 :          0 :     QgsDebugMsgLevel( QStringLiteral( "set GDAL_FIX_ESRI_WKT : %1" ).arg( configNew ), 4 );
     362                 :          0 :   }
     363                 :            :   else
     364                 :            :   {
     365                 :          0 :     QgsDebugMsgLevel( QStringLiteral( "GDAL_FIX_ESRI_WKT was already set : %1" ).arg( configNew ), 4 );
     366                 :            :   }
     367                 :          0 : }
     368                 :            : 
     369                 :         14 : bool QgsCoordinateReferenceSystem::createFromOgcWmsCrs( const QString &crs )
     370                 :            : {
     371                 :         14 :   if ( crs.isEmpty() )
     372                 :          0 :     return false;
     373                 :            : 
     374                 :         14 :   QgsReadWriteLocker locker( *sOgcLock(), QgsReadWriteLocker::Read );
     375                 :         14 :   if ( !sDisableOgcCache )
     376                 :            :   {
     377                 :         14 :     QHash< QString, QgsCoordinateReferenceSystem >::const_iterator crsIt = sOgcCache()->constFind( crs );
     378                 :         14 :     if ( crsIt != sOgcCache()->constEnd() )
     379                 :            :     {
     380                 :            :       // found a match in the cache
     381                 :          5 :       *this = crsIt.value();
     382                 :          5 :       return d->mIsValid;
     383                 :            :     }
     384                 :          9 :   }
     385                 :          9 :   locker.unlock();
     386                 :            : 
     387                 :          9 :   QString wmsCrs = crs;
     388                 :            : 
     389                 :         12 :   thread_local const QRegExp re_uri( QStringLiteral( "http://www\\.opengis\\.net/def/crs/([^/]+).+/([^/]+)" ), Qt::CaseInsensitive );
     390                 :         12 :   thread_local const QRegExp re_urn( QStringLiteral( "urn:ogc:def:crs:([^:]+).+([^:]+)" ), Qt::CaseInsensitive );
     391                 :          9 :   if ( re_uri.exactMatch( wmsCrs ) )
     392                 :            :   {
     393                 :          0 :     wmsCrs = re_uri.cap( 1 ) + ':' + re_uri.cap( 2 );
     394                 :          0 :   }
     395                 :          9 :   else if ( re_urn.exactMatch( wmsCrs ) )
     396                 :            :   {
     397                 :          0 :     wmsCrs = re_urn.cap( 1 ) + ':' + re_urn.cap( 2 );
     398                 :          0 :   }
     399                 :            :   else
     400                 :            :   {
     401                 :         12 :     thread_local const QRegExp re_urn_custom( QStringLiteral( "(user|custom|qgis):(\\d+)" ), Qt::CaseInsensitive );
     402                 :          9 :     if ( re_urn_custom.exactMatch( wmsCrs ) && createFromSrsId( re_urn_custom.cap( 2 ).toInt() ) )
     403                 :            :     {
     404                 :          0 :       locker.changeMode( QgsReadWriteLocker::Write );
     405                 :          0 :       if ( !sDisableOgcCache )
     406                 :          0 :         sOgcCache()->insert( crs, *this );
     407                 :          0 :       return d->mIsValid;
     408                 :            :     }
     409                 :            :   }
     410                 :            : 
     411                 :            :   // first chance for proj 6 - scan through legacy systems and try to use authid directly
     412                 :          9 :   const QString legacyKey = wmsCrs.toLower();
     413                 :      24698 :   for ( auto it = sAuthIdToQgisSrsIdMap.constBegin(); it != sAuthIdToQgisSrsIdMap.constEnd(); ++it )
     414                 :            :   {
     415                 :      24698 :     if ( it.key().compare( legacyKey, Qt::CaseInsensitive ) == 0 )
     416                 :            :     {
     417                 :          9 :       const QStringList parts = it.key().split( ':' );
     418                 :          9 :       const QString auth = parts.at( 0 );
     419                 :          9 :       const QString code = parts.at( 1 );
     420                 :          9 :       if ( loadFromAuthCode( auth, code ) )
     421                 :            :       {
     422                 :          9 :         locker.changeMode( QgsReadWriteLocker::Write );
     423                 :          9 :         if ( !sDisableOgcCache )
     424                 :          9 :           sOgcCache()->insert( crs, *this );
     425                 :          9 :         return d->mIsValid;
     426                 :            :       }
     427                 :          9 :     }
     428                 :      24689 :   }
     429                 :            : 
     430                 :          0 :   if ( loadFromDatabase( QgsApplication::srsDatabaseFilePath(), QStringLiteral( "lower(auth_name||':'||auth_id)" ), wmsCrs.toLower() ) )
     431                 :            :   {
     432                 :          0 :     locker.changeMode( QgsReadWriteLocker::Write );
     433                 :          0 :     if ( !sDisableOgcCache )
     434                 :          0 :       sOgcCache()->insert( crs, *this );
     435                 :          0 :     return d->mIsValid;
     436                 :            :   }
     437                 :            : 
     438                 :            :   // NAD27
     439                 :          0 :   if ( wmsCrs.compare( QLatin1String( "CRS:27" ), Qt::CaseInsensitive ) == 0 ||
     440                 :          0 :        wmsCrs.compare( QLatin1String( "OGC:CRS27" ), Qt::CaseInsensitive ) == 0 )
     441                 :            :   {
     442                 :            :     // TODO: verify same axis orientation
     443                 :          0 :     return createFromOgcWmsCrs( QStringLiteral( "EPSG:4267" ) );
     444                 :            :   }
     445                 :            : 
     446                 :            :   // NAD83
     447                 :          0 :   if ( wmsCrs.compare( QLatin1String( "CRS:83" ), Qt::CaseInsensitive ) == 0 ||
     448                 :          0 :        wmsCrs.compare( QLatin1String( "OGC:CRS83" ), Qt::CaseInsensitive ) == 0 )
     449                 :            :   {
     450                 :            :     // TODO: verify same axis orientation
     451                 :          0 :     return createFromOgcWmsCrs( QStringLiteral( "EPSG:4269" ) );
     452                 :            :   }
     453                 :            : 
     454                 :            :   // WGS84
     455                 :          0 :   if ( wmsCrs.compare( QLatin1String( "CRS:84" ), Qt::CaseInsensitive ) == 0 ||
     456                 :          0 :        wmsCrs.compare( QLatin1String( "OGC:CRS84" ), Qt::CaseInsensitive ) == 0 )
     457                 :            :   {
     458                 :          0 :     if ( loadFromDatabase( QgsApplication::srsDatabaseFilePath(), QStringLiteral( "lower(auth_name||':'||auth_id)" ), QStringLiteral( "epsg:4326" ) ) )
     459                 :            :     {
     460                 :          0 :       d->mAxisInverted = false;
     461                 :          0 :       d->mAxisInvertedDirty = false;
     462                 :          0 :     }
     463                 :            : 
     464                 :          0 :     locker.changeMode( QgsReadWriteLocker::Write );
     465                 :          0 :     if ( !sDisableOgcCache )
     466                 :          0 :       sOgcCache()->insert( crs, *this );
     467                 :            : 
     468                 :          0 :     return d->mIsValid;
     469                 :            :   }
     470                 :            : 
     471                 :          0 :   locker.changeMode( QgsReadWriteLocker::Write );
     472                 :          0 :   if ( !sDisableOgcCache )
     473                 :          0 :     sOgcCache()->insert( crs, QgsCoordinateReferenceSystem() );
     474                 :          0 :   return d->mIsValid;
     475                 :         14 : }
     476                 :            : 
     477                 :            : // Misc helper functions -----------------------
     478                 :            : 
     479                 :            : 
     480                 :         63 : void QgsCoordinateReferenceSystem::validate()
     481                 :            : {
     482                 :         63 :   if ( d->mIsValid || !sCustomSrsValidation )
     483                 :         63 :     return;
     484                 :            : 
     485                 :            :   // try to validate using custom validation routines
     486                 :          0 :   if ( sCustomSrsValidation )
     487                 :          0 :     sCustomSrsValidation( *this );
     488                 :         63 : }
     489                 :            : 
     490                 :          0 : bool QgsCoordinateReferenceSystem::createFromSrid( const long id )
     491                 :            : {
     492                 :          0 :   return createFromPostgisSrid( id );
     493                 :            : }
     494                 :            : 
     495                 :          0 : bool QgsCoordinateReferenceSystem::createFromPostgisSrid( const long id )
     496                 :            : {
     497                 :          0 :   QgsReadWriteLocker locker( *sSrIdCacheLock(), QgsReadWriteLocker::Read );
     498                 :          0 :   if ( !sDisableSrIdCache )
     499                 :            :   {
     500                 :          0 :     QHash< long, QgsCoordinateReferenceSystem >::const_iterator crsIt = sSrIdCache()->constFind( id );
     501                 :          0 :     if ( crsIt != sSrIdCache()->constEnd() )
     502                 :            :     {
     503                 :            :       // found a match in the cache
     504                 :          0 :       *this = crsIt.value();
     505                 :          0 :       return d->mIsValid;
     506                 :            :     }
     507                 :          0 :   }
     508                 :          0 :   locker.unlock();
     509                 :            : 
     510                 :            :   // first chance for proj 6 - scan through legacy systems and try to use authid directly
     511                 :          0 :   for ( auto it = sAuthIdToQgisSrsIdMap.constBegin(); it != sAuthIdToQgisSrsIdMap.constEnd(); ++it )
     512                 :            :   {
     513                 :          0 :     if ( it.value().endsWith( QStringLiteral( ",%1" ).arg( id ) ) )
     514                 :            :     {
     515                 :          0 :       const QStringList parts = it.key().split( ':' );
     516                 :          0 :       const QString auth = parts.at( 0 );
     517                 :          0 :       const QString code = parts.at( 1 );
     518                 :          0 :       if ( loadFromAuthCode( auth, code ) )
     519                 :            :       {
     520                 :          0 :         locker.changeMode( QgsReadWriteLocker::Write );
     521                 :          0 :         if ( !sDisableSrIdCache )
     522                 :          0 :           sSrIdCache()->insert( id, *this );
     523                 :            : 
     524                 :          0 :         return d->mIsValid;
     525                 :            :       }
     526                 :          0 :     }
     527                 :          0 :   }
     528                 :            : 
     529                 :          0 :   bool result = loadFromDatabase( QgsApplication::srsDatabaseFilePath(), QStringLiteral( "srid" ), QString::number( id ) );
     530                 :            : 
     531                 :          0 :   locker.changeMode( QgsReadWriteLocker::Write );
     532                 :          0 :   if ( !sDisableSrIdCache )
     533                 :          0 :     sSrIdCache()->insert( id, *this );
     534                 :            : 
     535                 :          0 :   return result;
     536                 :          0 : }
     537                 :            : 
     538                 :          0 : bool QgsCoordinateReferenceSystem::createFromSrsId( const long id )
     539                 :            : {
     540                 :          0 :   QgsReadWriteLocker locker( *sCRSSrsIdLock(), QgsReadWriteLocker::Read );
     541                 :          0 :   if ( !sDisableSrsIdCache )
     542                 :            :   {
     543                 :          0 :     QHash< long, QgsCoordinateReferenceSystem >::const_iterator crsIt = sSrsIdCache()->constFind( id );
     544                 :          0 :     if ( crsIt != sSrsIdCache()->constEnd() )
     545                 :            :     {
     546                 :            :       // found a match in the cache
     547                 :          0 :       *this = crsIt.value();
     548                 :          0 :       return d->mIsValid;
     549                 :            :     }
     550                 :          0 :   }
     551                 :          0 :   locker.unlock();
     552                 :            : 
     553                 :            :   // first chance for proj 6 - scan through legacy systems and try to use authid directly
     554                 :          0 :   for ( auto it = sAuthIdToQgisSrsIdMap.constBegin(); it != sAuthIdToQgisSrsIdMap.constEnd(); ++it )
     555                 :            :   {
     556                 :          0 :     if ( it.value().startsWith( QString::number( id ) + ',' ) )
     557                 :            :     {
     558                 :          0 :       const QStringList parts = it.key().split( ':' );
     559                 :          0 :       const QString auth = parts.at( 0 );
     560                 :          0 :       const QString code = parts.at( 1 );
     561                 :          0 :       if ( loadFromAuthCode( auth, code ) )
     562                 :            :       {
     563                 :          0 :         locker.changeMode( QgsReadWriteLocker::Write );
     564                 :          0 :         if ( !sDisableSrsIdCache )
     565                 :          0 :           sSrsIdCache()->insert( id, *this );
     566                 :          0 :         return d->mIsValid;
     567                 :            :       }
     568                 :          0 :     }
     569                 :          0 :   }
     570                 :            : 
     571                 :          0 :   bool result = loadFromDatabase( id < USER_CRS_START_ID ? QgsApplication::srsDatabaseFilePath() :
     572                 :          0 :                                   QgsApplication::qgisUserDatabaseFilePath(),
     573                 :          0 :                                   QStringLiteral( "srs_id" ), QString::number( id ) );
     574                 :            : 
     575                 :          0 :   locker.changeMode( QgsReadWriteLocker::Write );
     576                 :          0 :   if ( !sDisableSrsIdCache )
     577                 :          0 :     sSrsIdCache()->insert( id, *this );
     578                 :          0 :   return result;
     579                 :          0 : }
     580                 :            : 
     581                 :          0 : bool QgsCoordinateReferenceSystem::loadFromDatabase( const QString &db, const QString &expression, const QString &value )
     582                 :            : {
     583                 :          0 :   d.detach();
     584                 :            : 
     585                 :          0 :   QgsDebugMsgLevel( "load CRS from " + db + " where " + expression + " is " + value, 3 );
     586                 :          0 :   d->mIsValid = false;
     587                 :          0 :   d->mWktPreferred.clear();
     588                 :            : 
     589                 :          0 :   QFileInfo myInfo( db );
     590                 :          0 :   if ( !myInfo.exists() )
     591                 :            :   {
     592                 :          0 :     QgsDebugMsg( "failed : " + db + " does not exist!" );
     593                 :          0 :     return d->mIsValid;
     594                 :            :   }
     595                 :            : 
     596                 :          0 :   sqlite3_database_unique_ptr database;
     597                 :          0 :   sqlite3_statement_unique_ptr statement;
     598                 :            :   int           myResult;
     599                 :            :   //check the db is available
     600                 :          0 :   myResult = openDatabase( db, database );
     601                 :          0 :   if ( myResult != SQLITE_OK )
     602                 :            :   {
     603                 :          0 :     return d->mIsValid;
     604                 :            :   }
     605                 :            : 
     606                 :            :   /*
     607                 :            :     srs_id INTEGER PRIMARY KEY,
     608                 :            :     description text NOT NULL,
     609                 :            :     projection_acronym text NOT NULL,
     610                 :            :     ellipsoid_acronym NOT NULL,
     611                 :            :     parameters text NOT NULL,
     612                 :            :     srid integer NOT NULL,
     613                 :            :     auth_name varchar NOT NULL,
     614                 :            :     auth_id integer NOT NULL,
     615                 :            :     is_geo integer NOT NULL);
     616                 :            :   */
     617                 :            : 
     618                 :          0 :   QString mySql = "select srs_id,description,projection_acronym,"
     619                 :            :                   "ellipsoid_acronym,parameters,srid,auth_name||':'||auth_id,is_geo,wkt "
     620                 :          0 :                   "from tbl_srs where " + expression + '=' + QgsSqliteUtils::quotedString( value ) + " order by deprecated";
     621                 :          0 :   statement = database.prepare( mySql, myResult );
     622                 :          0 :   QString wkt;
     623                 :            :   // XXX Need to free memory from the error msg if one is set
     624                 :          0 :   if ( myResult == SQLITE_OK && statement.step() == SQLITE_ROW )
     625                 :            :   {
     626                 :          0 :     d->mSrsId = statement.columnAsText( 0 ).toLong();
     627                 :          0 :     d->mDescription = statement.columnAsText( 1 );
     628                 :          0 :     d->mProjectionAcronym = statement.columnAsText( 2 );
     629                 :          0 :     d->mEllipsoidAcronym.clear();
     630                 :          0 :     d->mProj4 = statement.columnAsText( 4 );
     631                 :          0 :     d->mWktPreferred.clear();
     632                 :          0 :     d->mSRID = statement.columnAsText( 5 ).toLong();
     633                 :          0 :     d->mAuthId = statement.columnAsText( 6 );
     634                 :          0 :     d->mIsGeographic = statement.columnAsText( 7 ).toInt() != 0;
     635                 :          0 :     wkt = statement.columnAsText( 8 );
     636                 :          0 :     d->mAxisInvertedDirty = true;
     637                 :            : 
     638                 :          0 :     if ( d->mSrsId >= USER_CRS_START_ID && ( d->mAuthId.isEmpty() || d->mAuthId == QChar( ':' ) ) )
     639                 :            :     {
     640                 :          0 :       d->mAuthId = QStringLiteral( "USER:%1" ).arg( d->mSrsId );
     641                 :          0 :     }
     642                 :          0 :     else if ( !d->mAuthId.startsWith( QLatin1String( "USER:" ), Qt::CaseInsensitive ) )
     643                 :            :     {
     644                 :          0 :       QStringList parts = d->mAuthId.split( ':' );
     645                 :          0 :       QString auth = parts.at( 0 );
     646                 :          0 :       QString code = parts.at( 1 );
     647                 :            : 
     648                 :            :       {
     649                 :          0 :         QgsProjUtils::proj_pj_unique_ptr crs( proj_create_from_database( QgsProjContext::get(), auth.toLatin1(), code.toLatin1(), PJ_CATEGORY_CRS, false, nullptr ) );
     650                 :          0 :         d->setPj( QgsProjUtils::crsToSingleCrs( crs.get() ) );
     651                 :          0 :       }
     652                 :            : 
     653                 :          0 :       d->mIsValid = d->hasPj();
     654                 :          0 :       setMapUnits();
     655                 :          0 :     }
     656                 :            : 
     657                 :          0 :     if ( !d->mIsValid )
     658                 :            :     {
     659                 :          0 :       if ( !wkt.isEmpty() )
     660                 :            :       {
     661                 :          0 :         setWktString( wkt );
     662                 :            :         // set WKT string resets the description to that description embedded in the WKT, so manually overwrite this back to the
     663                 :            :         // value from the user DB
     664                 :          0 :         d->mDescription = statement.columnAsText( 1 );
     665                 :          0 :       }
     666                 :            :       else
     667                 :          0 :         setProjString( d->mProj4 );
     668                 :          0 :     }
     669                 :          0 :   }
     670                 :            :   else
     671                 :            :   {
     672                 :          0 :     QgsDebugMsgLevel( "failed : " + mySql, 4 );
     673                 :            :   }
     674                 :          0 :   return d->mIsValid;
     675                 :          0 : }
     676                 :            : 
     677                 :          3 : void QgsCoordinateReferenceSystem::removeFromCacheObjectsBelongingToCurrentThread( PJ_CONTEXT *pj_context )
     678                 :            : {
     679                 :            :   // Not completely sure about object order destruction after main() has
     680                 :            :   // exited. So it is safer to check sDisableCache before using sCacheLock
     681                 :            :   // in case sCacheLock would have been destroyed before the current TLS
     682                 :            :   // QgsProjContext object that has called us...
     683                 :            : 
     684                 :          3 :   if ( !sDisableSrIdCache )
     685                 :            :   {
     686                 :          0 :     QgsReadWriteLocker locker( *sSrIdCacheLock(), QgsReadWriteLocker::Write );
     687                 :          0 :     if ( !sDisableSrIdCache )
     688                 :            :     {
     689                 :          0 :       for ( auto it = sSrIdCache()->begin(); it != sSrIdCache()->end(); )
     690                 :            :       {
     691                 :          0 :         auto &v = it.value();
     692                 :          0 :         if ( v.d->removeObjectsBelongingToCurrentThread( pj_context ) )
     693                 :          0 :           it = sSrIdCache()->erase( it );
     694                 :            :         else
     695                 :          0 :           ++it;
     696                 :            :       }
     697                 :          0 :     }
     698                 :          0 :   }
     699                 :          3 :   if ( !sDisableOgcCache )
     700                 :            :   {
     701                 :          0 :     QgsReadWriteLocker locker( *sOgcLock(), QgsReadWriteLocker::Write );
     702                 :          0 :     if ( !sDisableOgcCache )
     703                 :            :     {
     704                 :          0 :       for ( auto it = sOgcCache()->begin(); it != sOgcCache()->end(); )
     705                 :            :       {
     706                 :          0 :         auto &v = it.value();
     707                 :          0 :         if ( v.d->removeObjectsBelongingToCurrentThread( pj_context ) )
     708                 :          0 :           it = sOgcCache()->erase( it );
     709                 :            :         else
     710                 :          0 :           ++it;
     711                 :            :       }
     712                 :          0 :     }
     713                 :          0 :   }
     714                 :          3 :   if ( !sDisableProjCache )
     715                 :            :   {
     716                 :          0 :     QgsReadWriteLocker locker( *sProj4CacheLock(), QgsReadWriteLocker::Write );
     717                 :          0 :     if ( !sDisableProjCache )
     718                 :            :     {
     719                 :          0 :       for ( auto it = sProj4Cache()->begin(); it != sProj4Cache()->end(); )
     720                 :            :       {
     721                 :          0 :         auto &v = it.value();
     722                 :          0 :         if ( v.d->removeObjectsBelongingToCurrentThread( pj_context ) )
     723                 :          0 :           it = sProj4Cache()->erase( it );
     724                 :            :         else
     725                 :          0 :           ++it;
     726                 :            :       }
     727                 :          0 :     }
     728                 :          0 :   }
     729                 :          3 :   if ( !sDisableWktCache )
     730                 :            :   {
     731                 :          0 :     QgsReadWriteLocker locker( *sCRSWktLock(), QgsReadWriteLocker::Write );
     732                 :          0 :     if ( !sDisableWktCache )
     733                 :            :     {
     734                 :          0 :       for ( auto it = sWktCache()->begin(); it != sWktCache()->end(); )
     735                 :            :       {
     736                 :          0 :         auto &v = it.value();
     737                 :          0 :         if ( v.d->removeObjectsBelongingToCurrentThread( pj_context ) )
     738                 :          0 :           it = sWktCache()->erase( it );
     739                 :            :         else
     740                 :          0 :           ++it;
     741                 :            :       }
     742                 :          0 :     }
     743                 :          0 :   }
     744                 :          3 :   if ( !sDisableSrsIdCache )
     745                 :            :   {
     746                 :          0 :     QgsReadWriteLocker locker( *sCRSSrsIdLock(), QgsReadWriteLocker::Write );
     747                 :          0 :     if ( !sDisableSrsIdCache )
     748                 :            :     {
     749                 :          0 :       for ( auto it = sSrsIdCache()->begin(); it != sSrsIdCache()->end(); )
     750                 :            :       {
     751                 :          0 :         auto &v = it.value();
     752                 :          0 :         if ( v.d->removeObjectsBelongingToCurrentThread( pj_context ) )
     753                 :          0 :           it = sSrsIdCache()->erase( it );
     754                 :            :         else
     755                 :          0 :           ++it;
     756                 :            :       }
     757                 :          0 :     }
     758                 :          0 :   }
     759                 :          3 :   if ( !sDisableStringCache )
     760                 :            :   {
     761                 :          0 :     QgsReadWriteLocker locker( *sCrsStringLock(), QgsReadWriteLocker::Write );
     762                 :          0 :     if ( !sDisableStringCache )
     763                 :            :     {
     764                 :          0 :       for ( auto it = sStringCache()->begin(); it != sStringCache()->end(); )
     765                 :            :       {
     766                 :          0 :         auto &v = it.value();
     767                 :          0 :         if ( v.d->removeObjectsBelongingToCurrentThread( pj_context ) )
     768                 :          0 :           it = sStringCache()->erase( it );
     769                 :            :         else
     770                 :          0 :           ++it;
     771                 :            :       }
     772                 :          0 :     }
     773                 :          0 :   }
     774                 :          3 : }
     775                 :            : 
     776                 :          0 : bool QgsCoordinateReferenceSystem::hasAxisInverted() const
     777                 :            : {
     778                 :          0 :   if ( d->mAxisInvertedDirty )
     779                 :            :   {
     780                 :          0 :     d->mAxisInverted = QgsProjUtils::axisOrderIsSwapped( d->threadLocalProjObject() );
     781                 :          0 :     d->mAxisInvertedDirty = false;
     782                 :          0 :   }
     783                 :            : 
     784                 :          0 :   return d->mAxisInverted;
     785                 :            : }
     786                 :            : 
     787                 :          3 : bool QgsCoordinateReferenceSystem::createFromWkt( const QString &wkt )
     788                 :            : {
     789                 :          3 :   return createFromWktInternal( wkt, QString() );
     790                 :          0 : }
     791                 :            : 
     792                 :          3 : bool QgsCoordinateReferenceSystem::createFromWktInternal( const QString &wkt, const QString &description )
     793                 :            : {
     794                 :          3 :   if ( wkt.isEmpty() )
     795                 :          0 :     return false;
     796                 :            : 
     797                 :          3 :   d.detach();
     798                 :            : 
     799                 :          3 :   QgsReadWriteLocker locker( *sCRSWktLock(), QgsReadWriteLocker::Read );
     800                 :          3 :   if ( !sDisableWktCache )
     801                 :            :   {
     802                 :          3 :     QHash< QString, QgsCoordinateReferenceSystem >::const_iterator crsIt = sWktCache()->constFind( wkt );
     803                 :          3 :     if ( crsIt != sWktCache()->constEnd() )
     804                 :            :     {
     805                 :            :       // found a match in the cache
     806                 :          2 :       *this = crsIt.value();
     807                 :            : 
     808                 :          2 :       if ( !description.isEmpty() && d->mDescription.isEmpty() )
     809                 :            :       {
     810                 :            :         // now we have a name for a previously unknown CRS! Update the cached CRS accordingly, so that we use the name from now on...
     811                 :          0 :         d->mDescription = description;
     812                 :          0 :         locker.changeMode( QgsReadWriteLocker::Write );
     813                 :          0 :         sWktCache()->insert( wkt, *this );
     814                 :          0 :       }
     815                 :          2 :       return d->mIsValid;
     816                 :            :     }
     817                 :          1 :   }
     818                 :          1 :   locker.unlock();
     819                 :            : 
     820                 :          1 :   d->mIsValid = false;
     821                 :          1 :   d->mProj4.clear();
     822                 :          1 :   d->mWktPreferred.clear();
     823                 :          1 :   if ( wkt.isEmpty() )
     824                 :            :   {
     825                 :          0 :     QgsDebugMsgLevel( QStringLiteral( "theWkt is uninitialized, operation failed" ), 4 );
     826                 :          0 :     return d->mIsValid;
     827                 :            :   }
     828                 :            : 
     829                 :            :   // try to match against user crs
     830                 :          1 :   QgsCoordinateReferenceSystem::RecordMap record = getRecord( "select * from tbl_srs where wkt=" + QgsSqliteUtils::quotedString( wkt ) + " order by deprecated" );
     831                 :          1 :   if ( !record.empty() )
     832                 :            :   {
     833                 :          0 :     long srsId = record[QStringLiteral( "srs_id" )].toLong();
     834                 :          0 :     if ( srsId > 0 )
     835                 :            :     {
     836                 :          0 :       createFromSrsId( srsId );
     837                 :          0 :     }
     838                 :          0 :   }
     839                 :            :   else
     840                 :            :   {
     841                 :          1 :     setWktString( wkt );
     842                 :          1 :     if ( !description.isEmpty() )
     843                 :            :     {
     844                 :          0 :       d->mDescription = description;
     845                 :          0 :     }
     846                 :          1 :     if ( d->mSrsId == 0 )
     847                 :            :     {
     848                 :            :       // lastly, try a tolerant match of the created proj object against all user CRSes (allowing differences in parameter order during the comparison)
     849                 :          0 :       long id = matchToUserCrs();
     850                 :          0 :       if ( id >= USER_CRS_START_ID )
     851                 :            :       {
     852                 :          0 :         createFromSrsId( id );
     853                 :          0 :       }
     854                 :          0 :     }
     855                 :            :   }
     856                 :            : 
     857                 :          1 :   locker.changeMode( QgsReadWriteLocker::Write );
     858                 :          1 :   if ( !sDisableWktCache )
     859                 :          1 :     sWktCache()->insert( wkt, *this );
     860                 :            : 
     861                 :          1 :   return d->mIsValid;
     862                 :            :   //setMapunits will be called by createfromproj above
     863                 :          3 : }
     864                 :            : 
     865                 :       4544 : bool QgsCoordinateReferenceSystem::isValid() const
     866                 :            : {
     867                 :       4544 :   return d->mIsValid;
     868                 :            : }
     869                 :            : 
     870                 :          0 : bool QgsCoordinateReferenceSystem::createFromProj4( const QString &proj4String )
     871                 :            : {
     872                 :          0 :   return createFromProj( proj4String );
     873                 :            : }
     874                 :            : 
     875                 :          0 : bool QgsCoordinateReferenceSystem::createFromProj( const QString &projString, const bool identify )
     876                 :            : {
     877                 :          0 :   if ( projString.isEmpty() )
     878                 :          0 :     return false;
     879                 :            : 
     880                 :          0 :   d.detach();
     881                 :            : 
     882                 :          0 :   if ( projString.trimmed().isEmpty() )
     883                 :            :   {
     884                 :          0 :     d->mIsValid = false;
     885                 :          0 :     d->mProj4.clear();
     886                 :          0 :     d->mWktPreferred.clear();
     887                 :          0 :     return false;
     888                 :            :   }
     889                 :            : 
     890                 :          0 :   QgsReadWriteLocker locker( *sProj4CacheLock(), QgsReadWriteLocker::Read );
     891                 :          0 :   if ( !sDisableProjCache )
     892                 :            :   {
     893                 :          0 :     QHash< QString, QgsCoordinateReferenceSystem >::const_iterator crsIt = sProj4Cache()->constFind( projString );
     894                 :          0 :     if ( crsIt != sProj4Cache()->constEnd() )
     895                 :            :     {
     896                 :            :       // found a match in the cache
     897                 :          0 :       *this = crsIt.value();
     898                 :          0 :       return d->mIsValid;
     899                 :            :     }
     900                 :          0 :   }
     901                 :          0 :   locker.unlock();
     902                 :            : 
     903                 :            :   //
     904                 :            :   // Examples:
     905                 :            :   // +proj=tmerc +lat_0=0 +lon_0=-62 +k=0.999500 +x_0=400000 +y_0=0
     906                 :            :   // +ellps=clrk80 +towgs84=-255,-15,71,0,0,0,0 +units=m +no_defs
     907                 :            :   //
     908                 :            :   // +proj=lcc +lat_1=46.8 +lat_0=46.8 +lon_0=2.337229166666664 +k_0=0.99987742
     909                 :            :   // +x_0=600000 +y_0=2200000 +a=6378249.2 +b=6356515.000000472 +units=m +no_defs
     910                 :            :   //
     911                 :          0 :   QString myProj4String = projString.trimmed();
     912                 :          0 :   myProj4String.remove( QStringLiteral( "+type=crs" ) );
     913                 :          0 :   myProj4String = myProj4String.trimmed();
     914                 :            : 
     915                 :          0 :   d->mIsValid = false;
     916                 :          0 :   d->mWktPreferred.clear();
     917                 :            : 
     918                 :          0 :   if ( identify )
     919                 :            :   {
     920                 :            :     // first, try to use proj to do this for us...
     921                 :          0 :     const QString projCrsString = myProj4String + ( myProj4String.contains( QStringLiteral( "+type=crs" ) ) ? QString() : QStringLiteral( " +type=crs" ) );
     922                 :          0 :     QgsProjUtils::proj_pj_unique_ptr crs( proj_create( QgsProjContext::get(), projCrsString.toLatin1().constData() ) );
     923                 :          0 :     if ( crs )
     924                 :            :     {
     925                 :          0 :       QString authName;
     926                 :          0 :       QString authCode;
     927                 :          0 :       if ( QgsProjUtils::identifyCrs( crs.get(), authName, authCode, QgsProjUtils::FlagMatchBoundCrsToUnderlyingSourceCrs ) )
     928                 :            :       {
     929                 :          0 :         const QString authid = QStringLiteral( "%1:%2" ).arg( authName, authCode );
     930                 :          0 :         if ( createFromOgcWmsCrs( authid ) )
     931                 :            :         {
     932                 :          0 :           locker.changeMode( QgsReadWriteLocker::Write );
     933                 :          0 :           if ( !sDisableProjCache )
     934                 :          0 :             sProj4Cache()->insert( projString, *this );
     935                 :          0 :           return d->mIsValid;
     936                 :            :         }
     937                 :          0 :       }
     938                 :          0 :     }
     939                 :            : 
     940                 :            :     // try a direct match against user crses
     941                 :          0 :     QgsCoordinateReferenceSystem::RecordMap myRecord = getRecord( "select * from tbl_srs where parameters=" + QgsSqliteUtils::quotedString( myProj4String ) + " order by deprecated" );
     942                 :          0 :     long id = 0;
     943                 :          0 :     if ( !myRecord.empty() )
     944                 :            :     {
     945                 :          0 :       id = myRecord[QStringLiteral( "srs_id" )].toLong();
     946                 :          0 :       if ( id >= USER_CRS_START_ID )
     947                 :            :       {
     948                 :          0 :         createFromSrsId( id );
     949                 :          0 :       }
     950                 :          0 :     }
     951                 :          0 :     if ( id < USER_CRS_START_ID )
     952                 :            :     {
     953                 :            :       // no direct matches, so go ahead and create a new proj object based on the proj string alone.
     954                 :          0 :       setProjString( myProj4String );
     955                 :            : 
     956                 :            :       // lastly, try a tolerant match of the created proj object against all user CRSes (allowing differences in parameter order during the comparison)
     957                 :          0 :       id = matchToUserCrs();
     958                 :          0 :       if ( id >= USER_CRS_START_ID )
     959                 :            :       {
     960                 :          0 :         createFromSrsId( id );
     961                 :          0 :       }
     962                 :          0 :     }
     963                 :          0 :   }
     964                 :            :   else
     965                 :            :   {
     966                 :          0 :     setProjString( myProj4String );
     967                 :            :   }
     968                 :            : 
     969                 :          0 :   locker.changeMode( QgsReadWriteLocker::Write );
     970                 :          0 :   if ( !sDisableProjCache )
     971                 :          0 :     sProj4Cache()->insert( projString, *this );
     972                 :            : 
     973                 :          0 :   return d->mIsValid;
     974                 :          0 : }
     975                 :            : 
     976                 :            : //private method meant for internal use by this class only
     977                 :          1 : QgsCoordinateReferenceSystem::RecordMap QgsCoordinateReferenceSystem::getRecord( const QString &sql )
     978                 :            : {
     979                 :          1 :   QString myDatabaseFileName;
     980                 :          1 :   QgsCoordinateReferenceSystem::RecordMap myMap;
     981                 :          1 :   QString myFieldName;
     982                 :          1 :   QString myFieldValue;
     983                 :          1 :   sqlite3_database_unique_ptr database;
     984                 :          1 :   sqlite3_statement_unique_ptr statement;
     985                 :            :   int           myResult;
     986                 :            : 
     987                 :            :   // Get the full path name to the sqlite3 spatial reference database.
     988                 :          1 :   myDatabaseFileName = QgsApplication::srsDatabaseFilePath();
     989                 :          1 :   QFileInfo myInfo( myDatabaseFileName );
     990                 :          1 :   if ( !myInfo.exists() )
     991                 :            :   {
     992                 :          0 :     QgsDebugMsg( "failed : " + myDatabaseFileName + " does not exist!" );
     993                 :          0 :     return myMap;
     994                 :            :   }
     995                 :            : 
     996                 :            :   //check the db is available
     997                 :          1 :   myResult = openDatabase( myDatabaseFileName, database );
     998                 :          1 :   if ( myResult != SQLITE_OK )
     999                 :            :   {
    1000                 :          0 :     return myMap;
    1001                 :            :   }
    1002                 :            : 
    1003                 :          1 :   statement = database.prepare( sql, myResult );
    1004                 :            :   // XXX Need to free memory from the error msg if one is set
    1005                 :          1 :   if ( myResult == SQLITE_OK && statement.step() == SQLITE_ROW )
    1006                 :            :   {
    1007                 :          0 :     int myColumnCount = statement.columnCount();
    1008                 :            :     //loop through each column in the record adding its expression name and value to the map
    1009                 :          0 :     for ( int myColNo = 0; myColNo < myColumnCount; myColNo++ )
    1010                 :            :     {
    1011                 :          0 :       myFieldName = statement.columnName( myColNo );
    1012                 :          0 :       myFieldValue = statement.columnAsText( myColNo );
    1013                 :          0 :       myMap[myFieldName] = myFieldValue;
    1014                 :          0 :     }
    1015                 :          0 :     if ( statement.step() != SQLITE_DONE )
    1016                 :            :     {
    1017                 :          0 :       QgsDebugMsgLevel( QStringLiteral( "Multiple records found in srs.db" ), 4 );
    1018                 :            :       //be less fussy on proj 6 -- the db has MANY more entries!
    1019                 :          0 :     }
    1020                 :          0 :   }
    1021                 :            :   else
    1022                 :            :   {
    1023                 :          1 :     QgsDebugMsgLevel( "failed :  " + sql, 4 );
    1024                 :            :   }
    1025                 :            : 
    1026                 :          1 :   if ( myMap.empty() )
    1027                 :            :   {
    1028                 :          1 :     myDatabaseFileName = QgsApplication::qgisUserDatabaseFilePath();
    1029                 :          1 :     QFileInfo myFileInfo;
    1030                 :          1 :     myFileInfo.setFile( myDatabaseFileName );
    1031                 :          1 :     if ( !myFileInfo.exists() )
    1032                 :            :     {
    1033                 :          0 :       QgsDebugMsg( QStringLiteral( "user qgis.db not found" ) );
    1034                 :          0 :       return myMap;
    1035                 :            :     }
    1036                 :            : 
    1037                 :            :     //check the db is available
    1038                 :          1 :     myResult = openDatabase( myDatabaseFileName, database );
    1039                 :          1 :     if ( myResult != SQLITE_OK )
    1040                 :            :     {
    1041                 :          0 :       return myMap;
    1042                 :            :     }
    1043                 :            : 
    1044                 :          1 :     statement = database.prepare( sql, myResult );
    1045                 :            :     // XXX Need to free memory from the error msg if one is set
    1046                 :          1 :     if ( myResult == SQLITE_OK && statement.step() == SQLITE_ROW )
    1047                 :            :     {
    1048                 :          0 :       int myColumnCount = statement.columnCount();
    1049                 :            :       //loop through each column in the record adding its field name and value to the map
    1050                 :          0 :       for ( int myColNo = 0; myColNo < myColumnCount; myColNo++ )
    1051                 :            :       {
    1052                 :          0 :         myFieldName = statement.columnName( myColNo );
    1053                 :          0 :         myFieldValue = statement.columnAsText( myColNo );
    1054                 :          0 :         myMap[myFieldName] = myFieldValue;
    1055                 :          0 :       }
    1056                 :            : 
    1057                 :          0 :       if ( statement.step() != SQLITE_DONE )
    1058                 :            :       {
    1059                 :          0 :         QgsDebugMsgLevel( QStringLiteral( "Multiple records found in srs.db" ), 4 );
    1060                 :          0 :         myMap.clear();
    1061                 :          0 :       }
    1062                 :          0 :     }
    1063                 :            :     else
    1064                 :            :     {
    1065                 :          1 :       QgsDebugMsgLevel( "failed :  " + sql, 4 );
    1066                 :            :     }
    1067                 :          1 :   }
    1068                 :          1 :   return myMap;
    1069                 :          1 : }
    1070                 :            : 
    1071                 :            : // Accessors -----------------------------------
    1072                 :            : 
    1073                 :          0 : long QgsCoordinateReferenceSystem::srsid() const
    1074                 :            : {
    1075                 :          0 :   return d->mSrsId;
    1076                 :            : }
    1077                 :            : 
    1078                 :          0 : long QgsCoordinateReferenceSystem::postgisSrid() const
    1079                 :            : {
    1080                 :          0 :   return d->mSRID;
    1081                 :            : }
    1082                 :            : 
    1083                 :        260 : QString QgsCoordinateReferenceSystem::authid() const
    1084                 :            : {
    1085                 :        260 :   return d->mAuthId;
    1086                 :            : }
    1087                 :            : 
    1088                 :          1 : QString QgsCoordinateReferenceSystem::description() const
    1089                 :            : {
    1090                 :          1 :   if ( d->mDescription.isNull() )
    1091                 :            :   {
    1092                 :          1 :     return QString();
    1093                 :            :   }
    1094                 :            :   else
    1095                 :            :   {
    1096                 :          0 :     return d->mDescription;
    1097                 :            :   }
    1098                 :          1 : }
    1099                 :            : 
    1100                 :          0 : QString QgsCoordinateReferenceSystem::userFriendlyIdentifier( IdentifierType type ) const
    1101                 :            : {
    1102                 :          0 :   if ( !authid().isEmpty() )
    1103                 :            :   {
    1104                 :          0 :     if ( type != ShortString && !description().isEmpty() )
    1105                 :          0 :       return QStringLiteral( "%1 - %2" ).arg( authid(), description() );
    1106                 :          0 :     return authid();
    1107                 :            :   }
    1108                 :          0 :   else if ( !description().isEmpty() )
    1109                 :          0 :     return description();
    1110                 :          0 :   else if ( type == ShortString )
    1111                 :          0 :     return isValid() ? QObject::tr( "Custom CRS" ) : QObject::tr( "Unknown CRS" );
    1112                 :          0 :   else if ( !toWkt( WKT_PREFERRED ).isEmpty() )
    1113                 :          0 :     return QObject::tr( "Custom CRS: %1" ).arg(
    1114                 :          0 :              type == MediumString ? ( toWkt( WKT_PREFERRED ).left( 50 ) + QString( QChar( 0x2026 ) ) )
    1115                 :          0 :              : toWkt( WKT_PREFERRED ) );
    1116                 :          0 :   else if ( !toProj().isEmpty() )
    1117                 :          0 :     return QObject::tr( "Custom CRS: %1" ).arg( type == MediumString ? ( toProj().left( 50 ) + QString( QChar( 0x2026 ) ) )
    1118                 :          0 :            : toProj() );
    1119                 :            :   else
    1120                 :          0 :     return QString();
    1121                 :          0 : }
    1122                 :            : 
    1123                 :          1 : QString QgsCoordinateReferenceSystem::projectionAcronym() const
    1124                 :            : {
    1125                 :          1 :   if ( d->mProjectionAcronym.isNull() )
    1126                 :            :   {
    1127                 :          1 :     return QString();
    1128                 :            :   }
    1129                 :            :   else
    1130                 :            :   {
    1131                 :          0 :     return d->mProjectionAcronym;
    1132                 :            :   }
    1133                 :          1 : }
    1134                 :            : 
    1135                 :          1 : QString QgsCoordinateReferenceSystem::ellipsoidAcronym() const
    1136                 :            : {
    1137                 :          1 :   if ( d->mEllipsoidAcronym.isNull() )
    1138                 :            :   {
    1139                 :          1 :     if ( PJ *obj = d->threadLocalProjObject() )
    1140                 :            :     {
    1141                 :          0 :       QgsProjUtils::proj_pj_unique_ptr ellipsoid( proj_get_ellipsoid( QgsProjContext::get(), obj ) );
    1142                 :          0 :       if ( ellipsoid )
    1143                 :            :       {
    1144                 :          0 :         const QString ellipsoidAuthName( proj_get_id_auth_name( ellipsoid.get(), 0 ) );
    1145                 :          0 :         const QString ellipsoidAuthCode( proj_get_id_code( ellipsoid.get(), 0 ) );
    1146                 :          0 :         if ( !ellipsoidAuthName.isEmpty() && !ellipsoidAuthCode.isEmpty() )
    1147                 :          0 :           d->mEllipsoidAcronym = QStringLiteral( "%1:%2" ).arg( ellipsoidAuthName, ellipsoidAuthCode );
    1148                 :            :         else
    1149                 :            :         {
    1150                 :            :           double semiMajor, semiMinor, invFlattening;
    1151                 :          0 :           int semiMinorComputed = 0;
    1152                 :          0 :           if ( proj_ellipsoid_get_parameters( QgsProjContext::get(), ellipsoid.get(), &semiMajor, &semiMinor, &semiMinorComputed, &invFlattening ) )
    1153                 :            :           {
    1154                 :          0 :             d->mEllipsoidAcronym = QStringLiteral( "PARAMETER:%1:%2" ).arg( qgsDoubleToString( semiMajor ),
    1155                 :          0 :                                    qgsDoubleToString( semiMinor ) );
    1156                 :          0 :           }
    1157                 :            :           else
    1158                 :            :           {
    1159                 :          0 :             d->mEllipsoidAcronym.clear();
    1160                 :            :           }
    1161                 :            :         }
    1162                 :          0 :       }
    1163                 :          0 :     }
    1164                 :          1 :     return d->mEllipsoidAcronym;
    1165                 :            :   }
    1166                 :            :   else
    1167                 :            :   {
    1168                 :          0 :     return d->mEllipsoidAcronym;
    1169                 :            :   }
    1170                 :          1 : }
    1171                 :            : 
    1172                 :          0 : QString QgsCoordinateReferenceSystem::toProj4() const
    1173                 :            : {
    1174                 :          0 :   return toProj();
    1175                 :            : }
    1176                 :            : 
    1177                 :          2 : QString QgsCoordinateReferenceSystem::toProj() const
    1178                 :            : {
    1179                 :          2 :   if ( !d->mIsValid )
    1180                 :          2 :     return QString();
    1181                 :            : 
    1182                 :          0 :   if ( d->mProj4.isEmpty() )
    1183                 :            :   {
    1184                 :          0 :     if ( PJ *obj = d->threadLocalProjObject() )
    1185                 :            :     {
    1186                 :          0 :       d->mProj4 = getFullProjString( obj );
    1187                 :          0 :     }
    1188                 :          0 :   }
    1189                 :            :   // Stray spaces at the end?
    1190                 :          0 :   return d->mProj4.trimmed();
    1191                 :          2 : }
    1192                 :            : 
    1193                 :          0 : bool QgsCoordinateReferenceSystem::isGeographic() const
    1194                 :            : {
    1195                 :          0 :   return d->mIsGeographic;
    1196                 :            : }
    1197                 :            : 
    1198                 :          1 : QgsUnitTypes::DistanceUnit QgsCoordinateReferenceSystem::mapUnits() const
    1199                 :            : {
    1200                 :          1 :   if ( !d->mIsValid )
    1201                 :          1 :     return QgsUnitTypes::DistanceUnknownUnit;
    1202                 :            : 
    1203                 :          0 :   return d->mMapUnits;
    1204                 :          1 : }
    1205                 :            : 
    1206                 :          0 : QgsRectangle QgsCoordinateReferenceSystem::bounds() const
    1207                 :            : {
    1208                 :          0 :   if ( !d->mIsValid )
    1209                 :          0 :     return QgsRectangle();
    1210                 :            : 
    1211                 :          0 :   PJ *obj = d->threadLocalProjObject();
    1212                 :          0 :   if ( !obj )
    1213                 :          0 :     return QgsRectangle();
    1214                 :            : 
    1215                 :          0 :   double westLon = 0;
    1216                 :          0 :   double southLat = 0;
    1217                 :          0 :   double eastLon = 0;
    1218                 :          0 :   double northLat = 0;
    1219                 :            : 
    1220                 :          0 :   if ( !proj_get_area_of_use( QgsProjContext::get(), obj,
    1221                 :            :                               &westLon, &southLat, &eastLon, &northLat, nullptr ) )
    1222                 :          0 :     return QgsRectangle();
    1223                 :            : 
    1224                 :            : 
    1225                 :            :   // don't use the constructor which normalizes!
    1226                 :          0 :   QgsRectangle rect;
    1227                 :          0 :   rect.setXMinimum( westLon );
    1228                 :          0 :   rect.setYMinimum( southLat );
    1229                 :          0 :   rect.setXMaximum( eastLon );
    1230                 :          0 :   rect.setYMaximum( northLat );
    1231                 :          0 :   return rect;
    1232                 :          0 : }
    1233                 :            : 
    1234                 :          0 : void QgsCoordinateReferenceSystem::updateDefinition()
    1235                 :            : {
    1236                 :          0 :   if ( !d->mIsValid )
    1237                 :          0 :     return;
    1238                 :            : 
    1239                 :          0 :   if ( d->mSrsId >= USER_CRS_START_ID )
    1240                 :            :   {
    1241                 :            :     // user CRS, so update to new definition
    1242                 :          0 :     createFromSrsId( d->mSrsId );
    1243                 :          0 :   }
    1244                 :            :   else
    1245                 :            :   {
    1246                 :            :     // nothing to do -- only user CRS definitions can be changed
    1247                 :            :   }
    1248                 :          0 : }
    1249                 :            : 
    1250                 :          0 : void QgsCoordinateReferenceSystem::setProjString( const QString &proj4String )
    1251                 :            : {
    1252                 :          0 :   d.detach();
    1253                 :          0 :   d->mProj4 = proj4String;
    1254                 :          0 :   d->mWktPreferred.clear();
    1255                 :            : 
    1256                 :          0 :   QgsLocaleNumC l;
    1257                 :          0 :   QString trimmed = proj4String.trimmed();
    1258                 :            : 
    1259                 :          0 :   trimmed += QLatin1String( " +type=crs" );
    1260                 :          0 :   PJ_CONTEXT *ctx = QgsProjContext::get();
    1261                 :            : 
    1262                 :            :   {
    1263                 :          0 :     d->setPj( QgsProjUtils::proj_pj_unique_ptr( proj_create( ctx, trimmed.toLatin1().constData() ) ) );
    1264                 :            :   }
    1265                 :            : 
    1266                 :          0 :   if ( !d->hasPj() )
    1267                 :            :   {
    1268                 :            : #ifdef QGISDEBUG
    1269                 :            :     const int errNo = proj_context_errno( ctx );
    1270                 :            :     QgsDebugMsg( QStringLiteral( "proj string rejected: %1" ).arg( proj_errno_string( errNo ) ) );
    1271                 :            : #endif
    1272                 :          0 :     d->mIsValid = false;
    1273                 :          0 :   }
    1274                 :            :   else
    1275                 :            :   {
    1276                 :          0 :     d->mEllipsoidAcronym.clear();
    1277                 :          0 :     d->mIsValid = true;
    1278                 :            :   }
    1279                 :            : 
    1280                 :          0 :   setMapUnits();
    1281                 :          0 : }
    1282                 :            : 
    1283                 :          1 : bool QgsCoordinateReferenceSystem::setWktString( const QString &wkt )
    1284                 :            : {
    1285                 :          1 :   bool res = false;
    1286                 :          1 :   d->mIsValid = false;
    1287                 :          1 :   d->mWktPreferred.clear();
    1288                 :            : 
    1289                 :          1 :   PROJ_STRING_LIST warnings = nullptr;
    1290                 :          1 :   PROJ_STRING_LIST grammerErrors = nullptr;
    1291                 :            :   {
    1292                 :          1 :     d->setPj( QgsProjUtils::proj_pj_unique_ptr( proj_create_from_wkt( QgsProjContext::get(), wkt.toLatin1().constData(), nullptr, &warnings, &grammerErrors ) ) );
    1293                 :            :   }
    1294                 :            : 
    1295                 :          1 :   res = d->hasPj();
    1296                 :          1 :   if ( !res )
    1297                 :            :   {
    1298                 :          0 :     QgsDebugMsg( QStringLiteral( "\n---------------------------------------------------------------" ) );
    1299                 :          0 :     QgsDebugMsg( QStringLiteral( "This CRS could *** NOT *** be set from the supplied Wkt " ) );
    1300                 :          0 :     QgsDebugMsg( "INPUT: " + wkt );
    1301                 :          0 :     for ( auto iter = warnings; iter && *iter; ++iter )
    1302                 :          0 :       QgsDebugMsg( *iter );
    1303                 :          0 :     for ( auto iter = grammerErrors; iter && *iter; ++iter )
    1304                 :          0 :       QgsDebugMsg( *iter );
    1305                 :          0 :     QgsDebugMsg( QStringLiteral( "---------------------------------------------------------------\n" ) );
    1306                 :          0 :   }
    1307                 :          1 :   proj_string_list_destroy( warnings );
    1308                 :          1 :   proj_string_list_destroy( grammerErrors );
    1309                 :            : 
    1310                 :          1 :   QgsReadWriteLocker locker( *sProj4CacheLock(), QgsReadWriteLocker::Unlocked );
    1311                 :          1 :   if ( !res )
    1312                 :            :   {
    1313                 :          0 :     locker.changeMode( QgsReadWriteLocker::Write );
    1314                 :          0 :     if ( !sDisableWktCache )
    1315                 :          0 :       sWktCache()->insert( wkt, *this );
    1316                 :          0 :     return d->mIsValid;
    1317                 :            :   }
    1318                 :            : 
    1319                 :          1 :   if ( d->hasPj() )
    1320                 :            :   {
    1321                 :            :     // try 1 - maybe we can directly grab the auth name and code from the crs already?
    1322                 :          1 :     QString authName( proj_get_id_auth_name( d->threadLocalProjObject(), 0 ) );
    1323                 :          1 :     QString authCode( proj_get_id_code( d->threadLocalProjObject(), 0 ) );
    1324                 :            : 
    1325                 :          1 :     if ( authName.isEmpty() || authCode.isEmpty() )
    1326                 :            :     {
    1327                 :            :       // try 2, use proj's identify method and see if there's a nice candidate we can use
    1328                 :          0 :       QgsProjUtils::identifyCrs( d->threadLocalProjObject(), authName, authCode );
    1329                 :          0 :     }
    1330                 :            : 
    1331                 :          1 :     if ( !authName.isEmpty() && !authCode.isEmpty() )
    1332                 :            :     {
    1333                 :          1 :       if ( loadFromAuthCode( authName, authCode ) )
    1334                 :            :       {
    1335                 :          1 :         locker.changeMode( QgsReadWriteLocker::Write );
    1336                 :          1 :         if ( !sDisableWktCache )
    1337                 :          1 :           sWktCache()->insert( wkt, *this );
    1338                 :          1 :         return d->mIsValid;
    1339                 :            :       }
    1340                 :          0 :     }
    1341                 :            :     else
    1342                 :            :     {
    1343                 :            :       // Still a valid CRS, just not a known one
    1344                 :          0 :       d->mIsValid = true;
    1345                 :          0 :       d->mDescription = QString( proj_get_name( d->threadLocalProjObject() ) );
    1346                 :            :     }
    1347                 :          0 :     setMapUnits();
    1348                 :          1 :   }
    1349                 :            : 
    1350                 :          0 :   return d->mIsValid;
    1351                 :          1 : }
    1352                 :            : 
    1353                 :         10 : void QgsCoordinateReferenceSystem::setMapUnits()
    1354                 :            : {
    1355                 :         10 :   if ( !d->mIsValid )
    1356                 :            :   {
    1357                 :          0 :     d->mMapUnits = QgsUnitTypes::DistanceUnknownUnit;
    1358                 :          0 :     return;
    1359                 :            :   }
    1360                 :            : 
    1361                 :         10 :   if ( !d->hasPj() )
    1362                 :            :   {
    1363                 :          0 :     d->mMapUnits = QgsUnitTypes::DistanceUnknownUnit;
    1364                 :          0 :     return;
    1365                 :            :   }
    1366                 :            : 
    1367                 :         10 :   PJ_CONTEXT *context = QgsProjContext::get();
    1368                 :         10 :   QgsProjUtils::proj_pj_unique_ptr crs( QgsProjUtils::crsToSingleCrs( d->threadLocalProjObject() ) );
    1369                 :         10 :   QgsProjUtils::proj_pj_unique_ptr coordinateSystem( proj_crs_get_coordinate_system( context, crs.get() ) );
    1370                 :         10 :   if ( !coordinateSystem )
    1371                 :            :   {
    1372                 :          0 :     d->mMapUnits = QgsUnitTypes::DistanceUnknownUnit;
    1373                 :          0 :     return;
    1374                 :            :   }
    1375                 :            : 
    1376                 :         10 :   const int axisCount = proj_cs_get_axis_count( context, coordinateSystem.get() );
    1377                 :         10 :   if ( axisCount > 0 )
    1378                 :            :   {
    1379                 :         10 :     const char *outUnitName = nullptr;
    1380                 :            :     // Read only first axis
    1381                 :         10 :     proj_cs_get_axis_info( context, coordinateSystem.get(), 0,
    1382                 :            :                            nullptr,
    1383                 :            :                            nullptr,
    1384                 :            :                            nullptr,
    1385                 :            :                            nullptr,
    1386                 :            :                            &outUnitName,
    1387                 :            :                            nullptr,
    1388                 :            :                            nullptr );
    1389                 :            : 
    1390                 :         10 :     const QString unitName( outUnitName );
    1391                 :            : 
    1392                 :            :     // proj unit names are freeform -- they differ from authority to authority :(
    1393                 :            :     // see https://lists.osgeo.org/pipermail/proj/2019-April/008444.html
    1394                 :         15 :     if ( unitName.compare( QLatin1String( "degree" ), Qt::CaseInsensitive ) == 0 ||
    1395                 :          5 :          unitName.compare( QLatin1String( "degree minute second" ), Qt::CaseInsensitive ) == 0 ||
    1396                 :          5 :          unitName.compare( QLatin1String( "degree minute second hemisphere" ), Qt::CaseInsensitive ) == 0 ||
    1397                 :          5 :          unitName.compare( QLatin1String( "degree minute" ), Qt::CaseInsensitive ) == 0 ||
    1398                 :          5 :          unitName.compare( QLatin1String( "degree hemisphere" ), Qt::CaseInsensitive ) == 0 ||
    1399                 :          5 :          unitName.compare( QLatin1String( "degree minute hemisphere" ), Qt::CaseInsensitive ) == 0 ||
    1400                 :          5 :          unitName.compare( QLatin1String( "hemisphere degree" ), Qt::CaseInsensitive ) == 0 ||
    1401                 :          5 :          unitName.compare( QLatin1String( "hemisphere degree minute" ), Qt::CaseInsensitive ) == 0 ||
    1402                 :          5 :          unitName.compare( QLatin1String( "hemisphere degree minute second" ), Qt::CaseInsensitive ) == 0 ||
    1403                 :          5 :          unitName.compare( QLatin1String( "degree (supplier to define representation)" ), Qt::CaseInsensitive ) == 0 )
    1404                 :          5 :       d->mMapUnits = QgsUnitTypes::DistanceDegrees;
    1405                 :          5 :     else if ( unitName.compare( QLatin1String( "metre" ), Qt::CaseInsensitive ) == 0
    1406                 :          5 :               || unitName.compare( QLatin1String( "m" ), Qt::CaseInsensitive ) == 0
    1407                 :          0 :               || unitName.compare( QLatin1String( "meter" ), Qt::CaseInsensitive ) == 0 )
    1408                 :          5 :       d->mMapUnits = QgsUnitTypes::DistanceMeters;
    1409                 :            :     // we don't differentiate between these, suck it imperial users!
    1410                 :          0 :     else if ( unitName.compare( QLatin1String( "US survey foot" ), Qt::CaseInsensitive ) == 0 ||
    1411                 :          0 :               unitName.compare( QLatin1String( "foot" ), Qt::CaseInsensitive ) == 0 )
    1412                 :          0 :       d->mMapUnits = QgsUnitTypes::DistanceFeet;
    1413                 :          0 :     else if ( unitName.compare( QLatin1String( "kilometre" ), Qt::CaseInsensitive ) == 0 )  //#spellok
    1414                 :          0 :       d->mMapUnits = QgsUnitTypes::DistanceKilometers;
    1415                 :          0 :     else if ( unitName.compare( QLatin1String( "centimetre" ), Qt::CaseInsensitive ) == 0 )  //#spellok
    1416                 :          0 :       d->mMapUnits = QgsUnitTypes::DistanceCentimeters;
    1417                 :          0 :     else if ( unitName.compare( QLatin1String( "millimetre" ), Qt::CaseInsensitive ) == 0 )  //#spellok
    1418                 :          0 :       d->mMapUnits = QgsUnitTypes::DistanceMillimeters;
    1419                 :          0 :     else if ( unitName.compare( QLatin1String( "Statute mile" ), Qt::CaseInsensitive ) == 0 )
    1420                 :          0 :       d->mMapUnits = QgsUnitTypes::DistanceMiles;
    1421                 :          0 :     else if ( unitName.compare( QLatin1String( "nautical mile" ), Qt::CaseInsensitive ) == 0 )
    1422                 :          0 :       d->mMapUnits = QgsUnitTypes::DistanceNauticalMiles;
    1423                 :          0 :     else if ( unitName.compare( QLatin1String( "yard" ), Qt::CaseInsensitive ) == 0 )
    1424                 :          0 :       d->mMapUnits = QgsUnitTypes::DistanceYards;
    1425                 :            :     // TODO - maybe more values to handle here?
    1426                 :            :     else
    1427                 :          0 :       d->mMapUnits = QgsUnitTypes::DistanceUnknownUnit;
    1428                 :            :     return;
    1429                 :         10 :   }
    1430                 :            :   else
    1431                 :            :   {
    1432                 :          0 :     d->mMapUnits = QgsUnitTypes::DistanceUnknownUnit;
    1433                 :          0 :     return;
    1434                 :            :   }
    1435                 :         10 : }
    1436                 :            : 
    1437                 :            : 
    1438                 :          0 : long QgsCoordinateReferenceSystem::findMatchingProj()
    1439                 :            : {
    1440                 :          0 :   if ( d->mEllipsoidAcronym.isNull() || d->mProjectionAcronym.isNull()
    1441                 :          0 :        || !d->mIsValid )
    1442                 :            :   {
    1443                 :          0 :     QgsDebugMsgLevel( "QgsCoordinateReferenceSystem::findMatchingProj will only "
    1444                 :            :                       "work if prj acr ellipsoid acr and proj4string are set"
    1445                 :            :                       " and the current projection is valid!", 4 );
    1446                 :          0 :     return 0;
    1447                 :            :   }
    1448                 :            : 
    1449                 :          0 :   sqlite3_database_unique_ptr database;
    1450                 :          0 :   sqlite3_statement_unique_ptr statement;
    1451                 :            :   int myResult;
    1452                 :            : 
    1453                 :            :   // Set up the query to retrieve the projection information
    1454                 :            :   // needed to populate the list
    1455                 :          0 :   QString mySql = QString( "select srs_id,parameters from tbl_srs where "
    1456                 :            :                            "projection_acronym=%1 and ellipsoid_acronym=%2 order by deprecated" )
    1457                 :          0 :                   .arg( QgsSqliteUtils::quotedString( d->mProjectionAcronym ),
    1458                 :          0 :                         QgsSqliteUtils::quotedString( d->mEllipsoidAcronym ) );
    1459                 :            :   // Get the full path name to the sqlite3 spatial reference database.
    1460                 :          0 :   QString myDatabaseFileName = QgsApplication::srsDatabaseFilePath();
    1461                 :            : 
    1462                 :            :   //check the db is available
    1463                 :          0 :   myResult = openDatabase( myDatabaseFileName, database );
    1464                 :          0 :   if ( myResult != SQLITE_OK )
    1465                 :            :   {
    1466                 :          0 :     return 0;
    1467                 :            :   }
    1468                 :            : 
    1469                 :          0 :   statement = database.prepare( mySql, myResult );
    1470                 :          0 :   if ( myResult == SQLITE_OK )
    1471                 :            :   {
    1472                 :            : 
    1473                 :          0 :     while ( statement.step() == SQLITE_ROW )
    1474                 :            :     {
    1475                 :          0 :       QString mySrsId = statement.columnAsText( 0 );
    1476                 :          0 :       QString myProj4String = statement.columnAsText( 1 );
    1477                 :          0 :       if ( toProj() == myProj4String.trimmed() )
    1478                 :            :       {
    1479                 :          0 :         return mySrsId.toLong();
    1480                 :            :       }
    1481                 :          0 :     }
    1482                 :          0 :   }
    1483                 :            : 
    1484                 :            :   //
    1485                 :            :   // Try the users db now
    1486                 :            :   //
    1487                 :            : 
    1488                 :          0 :   myDatabaseFileName = QgsApplication::qgisUserDatabaseFilePath();
    1489                 :            :   //check the db is available
    1490                 :          0 :   myResult = openDatabase( myDatabaseFileName, database );
    1491                 :          0 :   if ( myResult != SQLITE_OK )
    1492                 :            :   {
    1493                 :          0 :     return 0;
    1494                 :            :   }
    1495                 :            : 
    1496                 :          0 :   statement = database.prepare( mySql, myResult );
    1497                 :            : 
    1498                 :          0 :   if ( myResult == SQLITE_OK )
    1499                 :            :   {
    1500                 :          0 :     while ( statement.step() == SQLITE_ROW )
    1501                 :            :     {
    1502                 :          0 :       QString mySrsId = statement.columnAsText( 0 );
    1503                 :          0 :       QString myProj4String = statement.columnAsText( 1 );
    1504                 :          0 :       if ( toProj() == myProj4String.trimmed() )
    1505                 :            :       {
    1506                 :          0 :         return mySrsId.toLong();
    1507                 :            :       }
    1508                 :          0 :     }
    1509                 :          0 :   }
    1510                 :            : 
    1511                 :          0 :   return 0;
    1512                 :          0 : }
    1513                 :            : 
    1514                 :       1536 : bool QgsCoordinateReferenceSystem::operator==( const QgsCoordinateReferenceSystem &srs ) const
    1515                 :            : {
    1516                 :            :   // shortcut
    1517                 :       1536 :   if ( d == srs.d )
    1518                 :          5 :     return true;
    1519                 :            : 
    1520                 :       1531 :   if ( !d->mIsValid && !srs.d->mIsValid )
    1521                 :          0 :     return true;
    1522                 :            : 
    1523                 :       1531 :   if ( !d->mIsValid || !srs.d->mIsValid )
    1524                 :       1517 :     return false;
    1525                 :            : 
    1526                 :         14 :   const bool isUser = d->mSrsId >= USER_CRS_START_ID;
    1527                 :         14 :   const bool otherIsUser = srs.d->mSrsId >= USER_CRS_START_ID;
    1528                 :         14 :   if ( isUser != otherIsUser )
    1529                 :          0 :     return false;
    1530                 :            : 
    1531                 :            :   // we can't directly compare authid for user crses -- the actual definition of these may have changed
    1532                 :         14 :   if ( !isUser && ( !d->mAuthId.isEmpty() || !srs.d->mAuthId.isEmpty() ) )
    1533                 :         14 :     return d->mAuthId == srs.d->mAuthId;
    1534                 :            : 
    1535                 :          0 :   return toWkt( WKT_PREFERRED ) == srs.toWkt( WKT_PREFERRED );
    1536                 :       1536 : }
    1537                 :            : 
    1538                 :       1534 : bool QgsCoordinateReferenceSystem::operator!=( const QgsCoordinateReferenceSystem &srs ) const
    1539                 :            : {
    1540                 :       1534 :   return  !( *this == srs );
    1541                 :            : }
    1542                 :            : 
    1543                 :          1 : QString QgsCoordinateReferenceSystem::toWkt( WktVariant variant, bool multiline, int indentationWidth ) const
    1544                 :            : {
    1545                 :          1 :   if ( PJ *obj = d->threadLocalProjObject() )
    1546                 :            :   {
    1547                 :          0 :     const bool isDefaultPreferredFormat = variant == WKT_PREFERRED && !multiline;
    1548                 :          0 :     if ( isDefaultPreferredFormat && !d->mWktPreferred.isEmpty() )
    1549                 :            :     {
    1550                 :            :       // can use cached value
    1551                 :          0 :       return d->mWktPreferred;
    1552                 :            :     }
    1553                 :            : 
    1554                 :          0 :     PJ_WKT_TYPE type = PJ_WKT1_GDAL;
    1555                 :          0 :     switch ( variant )
    1556                 :            :     {
    1557                 :            :       case WKT1_GDAL:
    1558                 :          0 :         type = PJ_WKT1_GDAL;
    1559                 :          0 :         break;
    1560                 :            :       case WKT1_ESRI:
    1561                 :          0 :         type = PJ_WKT1_ESRI;
    1562                 :          0 :         break;
    1563                 :            :       case WKT2_2015:
    1564                 :          0 :         type = PJ_WKT2_2015;
    1565                 :          0 :         break;
    1566                 :            :       case WKT2_2015_SIMPLIFIED:
    1567                 :          0 :         type = PJ_WKT2_2015_SIMPLIFIED;
    1568                 :          0 :         break;
    1569                 :            :       case WKT2_2019:
    1570                 :          0 :         type = PJ_WKT2_2019;
    1571                 :          0 :         break;
    1572                 :            :       case WKT2_2019_SIMPLIFIED:
    1573                 :          0 :         type = PJ_WKT2_2019_SIMPLIFIED;
    1574                 :          0 :         break;
    1575                 :            :     }
    1576                 :            : 
    1577                 :          0 :     const QByteArray multiLineOption = QStringLiteral( "MULTILINE=%1" ).arg( multiline ? QStringLiteral( "YES" ) : QStringLiteral( "NO" ) ).toLocal8Bit();
    1578                 :          0 :     const QByteArray indentatationWidthOption = QStringLiteral( "INDENTATION_WIDTH=%1" ).arg( multiline ? QString::number( indentationWidth ) : QStringLiteral( "0" ) ).toLocal8Bit();
    1579                 :          0 :     const char *const options[] = {multiLineOption.constData(), indentatationWidthOption.constData(), nullptr};
    1580                 :          0 :     QString res = proj_as_wkt( QgsProjContext::get(), obj, type, options );
    1581                 :            : 
    1582                 :          0 :     if ( isDefaultPreferredFormat )
    1583                 :            :     {
    1584                 :            :       // cache result for later use
    1585                 :          0 :       d->mWktPreferred = res;
    1586                 :          0 :     }
    1587                 :            : 
    1588                 :          0 :     return res;
    1589                 :          0 :   }
    1590                 :          1 :   return QString();
    1591                 :          1 : }
    1592                 :            : 
    1593                 :          0 : bool QgsCoordinateReferenceSystem::readXml( const QDomNode &node )
    1594                 :            : {
    1595                 :          0 :   d.detach();
    1596                 :          0 :   bool result = true;
    1597                 :          0 :   QDomNode srsNode = node.namedItem( QStringLiteral( "spatialrefsys" ) );
    1598                 :            : 
    1599                 :          0 :   if ( ! srsNode.isNull() )
    1600                 :            :   {
    1601                 :          0 :     bool initialized = false;
    1602                 :            : 
    1603                 :          0 :     bool ok = false;
    1604                 :          0 :     long srsid = srsNode.namedItem( QStringLiteral( "srsid" ) ).toElement().text().toLong( &ok );
    1605                 :            : 
    1606                 :          0 :     QDomNode node;
    1607                 :            : 
    1608                 :          0 :     if ( ok && srsid > 0 && srsid < USER_CRS_START_ID )
    1609                 :            :     {
    1610                 :          0 :       node = srsNode.namedItem( QStringLiteral( "authid" ) );
    1611                 :          0 :       if ( !node.isNull() )
    1612                 :            :       {
    1613                 :          0 :         createFromOgcWmsCrs( node.toElement().text() );
    1614                 :          0 :         if ( isValid() )
    1615                 :            :         {
    1616                 :          0 :           initialized = true;
    1617                 :          0 :         }
    1618                 :          0 :       }
    1619                 :            : 
    1620                 :          0 :       if ( !initialized )
    1621                 :            :       {
    1622                 :          0 :         node = srsNode.namedItem( QStringLiteral( "epsg" ) );
    1623                 :          0 :         if ( !node.isNull() )
    1624                 :            :         {
    1625                 :          0 :           operator=( QgsCoordinateReferenceSystem::fromEpsgId( node.toElement().text().toLong() ) );
    1626                 :          0 :           if ( isValid() )
    1627                 :            :           {
    1628                 :          0 :             initialized = true;
    1629                 :          0 :           }
    1630                 :          0 :         }
    1631                 :          0 :       }
    1632                 :          0 :     }
    1633                 :            : 
    1634                 :            :     // if wkt is present, prefer that since it's lossless (unlike proj4 strings)
    1635                 :          0 :     if ( !initialized )
    1636                 :            :     {
    1637                 :            :       // before doing anything, we grab and set the stored CRS name (description).
    1638                 :            :       // this way if the stored CRS doesn't match anything available locally (i.e. from Proj's db
    1639                 :            :       // or the user's custom CRS list), then we will correctly show the CRS with its original
    1640                 :            :       // name (instead of just "custom crs")
    1641                 :          0 :       const QString description = srsNode.namedItem( QStringLiteral( "description" ) ).toElement().text();
    1642                 :            : 
    1643                 :          0 :       const QString wkt = srsNode.namedItem( QStringLiteral( "wkt" ) ).toElement().text();
    1644                 :          0 :       initialized = createFromWktInternal( wkt, description );
    1645                 :          0 :     }
    1646                 :            : 
    1647                 :          0 :     if ( !initialized )
    1648                 :            :     {
    1649                 :          0 :       node = srsNode.namedItem( QStringLiteral( "proj4" ) );
    1650                 :          0 :       const QString proj4 = node.toElement().text();
    1651                 :          0 :       initialized = createFromProj( proj4 );
    1652                 :          0 :     }
    1653                 :            : 
    1654                 :          0 :     if ( !initialized )
    1655                 :            :     {
    1656                 :            :       // Setting from elements one by one
    1657                 :          0 :       node = srsNode.namedItem( QStringLiteral( "proj4" ) );
    1658                 :          0 :       const QString proj4 = node.toElement().text();
    1659                 :          0 :       if ( !proj4.trimmed().isEmpty() )
    1660                 :          0 :         setProjString( node.toElement().text() );
    1661                 :            : 
    1662                 :          0 :       node = srsNode.namedItem( QStringLiteral( "srsid" ) );
    1663                 :          0 :       d->mSrsId = node.toElement().text().toLong();
    1664                 :            : 
    1665                 :          0 :       node = srsNode.namedItem( QStringLiteral( "srid" ) );
    1666                 :          0 :       d->mSRID = node.toElement().text().toLong();
    1667                 :            : 
    1668                 :          0 :       node = srsNode.namedItem( QStringLiteral( "authid" ) );
    1669                 :          0 :       d->mAuthId = node.toElement().text();
    1670                 :            : 
    1671                 :          0 :       node = srsNode.namedItem( QStringLiteral( "description" ) );
    1672                 :          0 :       d->mDescription = node.toElement().text();
    1673                 :            : 
    1674                 :          0 :       node = srsNode.namedItem( QStringLiteral( "projectionacronym" ) );
    1675                 :          0 :       d->mProjectionAcronym = node.toElement().text();
    1676                 :            : 
    1677                 :          0 :       node = srsNode.namedItem( QStringLiteral( "ellipsoidacronym" ) );
    1678                 :          0 :       d->mEllipsoidAcronym = node.toElement().text();
    1679                 :            : 
    1680                 :          0 :       node = srsNode.namedItem( QStringLiteral( "geographicflag" ) );
    1681                 :          0 :       d->mIsGeographic = node.toElement().text().compare( QLatin1String( "true" ) );
    1682                 :            : 
    1683                 :          0 :       d->mWktPreferred.clear();
    1684                 :            : 
    1685                 :            :       //make sure the map units have been set
    1686                 :          0 :       setMapUnits();
    1687                 :          0 :     }
    1688                 :          0 :   }
    1689                 :            :   else
    1690                 :            :   {
    1691                 :            :     // Return empty CRS if none was found in the XML.
    1692                 :          0 :     d = new QgsCoordinateReferenceSystemPrivate();
    1693                 :          0 :     result = false;
    1694                 :            :   }
    1695                 :          0 :   return result;
    1696                 :          0 : }
    1697                 :            : 
    1698                 :          0 : bool QgsCoordinateReferenceSystem::writeXml( QDomNode &node, QDomDocument &doc ) const
    1699                 :            : {
    1700                 :          0 :   QDomElement layerNode = node.toElement();
    1701                 :          0 :   QDomElement srsElement = doc.createElement( QStringLiteral( "spatialrefsys" ) );
    1702                 :            : 
    1703                 :          0 :   QDomElement wktElement = doc.createElement( QStringLiteral( "wkt" ) );
    1704                 :          0 :   wktElement.appendChild( doc.createTextNode( toWkt( WKT_PREFERRED ) ) );
    1705                 :          0 :   srsElement.appendChild( wktElement );
    1706                 :            : 
    1707                 :          0 :   QDomElement proj4Element = doc.createElement( QStringLiteral( "proj4" ) );
    1708                 :          0 :   proj4Element.appendChild( doc.createTextNode( toProj() ) );
    1709                 :          0 :   srsElement.appendChild( proj4Element );
    1710                 :            : 
    1711                 :          0 :   QDomElement srsIdElement = doc.createElement( QStringLiteral( "srsid" ) );
    1712                 :          0 :   srsIdElement.appendChild( doc.createTextNode( QString::number( srsid() ) ) );
    1713                 :          0 :   srsElement.appendChild( srsIdElement );
    1714                 :            : 
    1715                 :          0 :   QDomElement sridElement = doc.createElement( QStringLiteral( "srid" ) );
    1716                 :          0 :   sridElement.appendChild( doc.createTextNode( QString::number( postgisSrid() ) ) );
    1717                 :          0 :   srsElement.appendChild( sridElement );
    1718                 :            : 
    1719                 :          0 :   QDomElement authidElement = doc.createElement( QStringLiteral( "authid" ) );
    1720                 :          0 :   authidElement.appendChild( doc.createTextNode( authid() ) );
    1721                 :          0 :   srsElement.appendChild( authidElement );
    1722                 :            : 
    1723                 :          0 :   QDomElement descriptionElement = doc.createElement( QStringLiteral( "description" ) );
    1724                 :          0 :   descriptionElement.appendChild( doc.createTextNode( description() ) );
    1725                 :          0 :   srsElement.appendChild( descriptionElement );
    1726                 :            : 
    1727                 :          0 :   QDomElement projectionAcronymElement = doc.createElement( QStringLiteral( "projectionacronym" ) );
    1728                 :          0 :   projectionAcronymElement.appendChild( doc.createTextNode( projectionAcronym() ) );
    1729                 :          0 :   srsElement.appendChild( projectionAcronymElement );
    1730                 :            : 
    1731                 :          0 :   QDomElement ellipsoidAcronymElement = doc.createElement( QStringLiteral( "ellipsoidacronym" ) );
    1732                 :          0 :   ellipsoidAcronymElement.appendChild( doc.createTextNode( ellipsoidAcronym() ) );
    1733                 :          0 :   srsElement.appendChild( ellipsoidAcronymElement );
    1734                 :            : 
    1735                 :          0 :   QDomElement geographicFlagElement = doc.createElement( QStringLiteral( "geographicflag" ) );
    1736                 :          0 :   QString geoFlagText = QStringLiteral( "false" );
    1737                 :          0 :   if ( isGeographic() )
    1738                 :            :   {
    1739                 :          0 :     geoFlagText = QStringLiteral( "true" );
    1740                 :          0 :   }
    1741                 :            : 
    1742                 :          0 :   geographicFlagElement.appendChild( doc.createTextNode( geoFlagText ) );
    1743                 :          0 :   srsElement.appendChild( geographicFlagElement );
    1744                 :            : 
    1745                 :          0 :   layerNode.appendChild( srsElement );
    1746                 :            : 
    1747                 :            :   return true;
    1748                 :          0 : }
    1749                 :            : 
    1750                 :            : //
    1751                 :            : // Static helper methods below this point only please!
    1752                 :            : //
    1753                 :            : 
    1754                 :            : 
    1755                 :            : // Returns the whole proj4 string for the selected srsid
    1756                 :            : //this is a static method! NOTE I've made it private for now to reduce API clutter TS
    1757                 :          0 : QString QgsCoordinateReferenceSystem::projFromSrsId( const int srsId )
    1758                 :            : {
    1759                 :          0 :   QString myDatabaseFileName;
    1760                 :          0 :   QString myProjString;
    1761                 :          0 :   QString mySql = QStringLiteral( "select parameters from tbl_srs where srs_id = %1 order by deprecated" ).arg( srsId );
    1762                 :            : 
    1763                 :            :   //
    1764                 :            :   // Determine if this is a user projection or a system on
    1765                 :            :   // user projection defs all have srs_id >= 100000
    1766                 :            :   //
    1767                 :          0 :   if ( srsId >= USER_CRS_START_ID )
    1768                 :            :   {
    1769                 :          0 :     myDatabaseFileName = QgsApplication::qgisUserDatabaseFilePath();
    1770                 :          0 :     QFileInfo myFileInfo;
    1771                 :          0 :     myFileInfo.setFile( myDatabaseFileName );
    1772                 :          0 :     if ( !myFileInfo.exists() ) //its unlikely that this condition will ever be reached
    1773                 :            :     {
    1774                 :          0 :       QgsDebugMsg( QStringLiteral( "users qgis.db not found" ) );
    1775                 :          0 :       return QString();
    1776                 :            :     }
    1777                 :          0 :   }
    1778                 :            :   else //must be  a system projection then
    1779                 :            :   {
    1780                 :          0 :     myDatabaseFileName = QgsApplication::srsDatabaseFilePath();
    1781                 :            :   }
    1782                 :            : 
    1783                 :          0 :   sqlite3_database_unique_ptr database;
    1784                 :          0 :   sqlite3_statement_unique_ptr statement;
    1785                 :            : 
    1786                 :            :   int rc;
    1787                 :          0 :   rc = openDatabase( myDatabaseFileName, database );
    1788                 :          0 :   if ( rc )
    1789                 :            :   {
    1790                 :          0 :     return QString();
    1791                 :            :   }
    1792                 :            : 
    1793                 :          0 :   statement = database.prepare( mySql, rc );
    1794                 :            : 
    1795                 :          0 :   if ( rc == SQLITE_OK )
    1796                 :            :   {
    1797                 :          0 :     if ( statement.step() == SQLITE_ROW )
    1798                 :            :     {
    1799                 :          0 :       myProjString = statement.columnAsText( 0 );
    1800                 :          0 :     }
    1801                 :          0 :   }
    1802                 :            : 
    1803                 :          0 :   return myProjString;
    1804                 :          0 : }
    1805                 :            : 
    1806                 :          2 : int QgsCoordinateReferenceSystem::openDatabase( const QString &path, sqlite3_database_unique_ptr &database, bool readonly )
    1807                 :            : {
    1808                 :            :   int myResult;
    1809                 :          2 :   if ( readonly )
    1810                 :          2 :     myResult = database.open_v2( path, SQLITE_OPEN_READONLY, nullptr );
    1811                 :            :   else
    1812                 :          0 :     myResult = database.open( path );
    1813                 :            : 
    1814                 :          2 :   if ( myResult != SQLITE_OK )
    1815                 :            :   {
    1816                 :          0 :     QgsDebugMsg( "Can't open database: " + database.errorMessage() );
    1817                 :            :     // XXX This will likely never happen since on open, sqlite creates the
    1818                 :            :     //     database if it does not exist.
    1819                 :            :     // ... unfortunately it happens on Windows
    1820                 :          0 :     QgsMessageLog::logMessage( QObject::tr( "Could not open CRS database %1\nError(%2): %3" )
    1821                 :          0 :                                .arg( path )
    1822                 :          0 :                                .arg( myResult )
    1823                 :          0 :                                .arg( database.errorMessage() ), QObject::tr( "CRS" ) );
    1824                 :          0 :   }
    1825                 :          2 :   return myResult;
    1826                 :          0 : }
    1827                 :            : 
    1828                 :          0 : void QgsCoordinateReferenceSystem::setCustomCrsValidation( CUSTOM_CRS_VALIDATION f )
    1829                 :            : {
    1830                 :          0 :   sCustomSrsValidation = f;
    1831                 :          0 : }
    1832                 :            : 
    1833                 :          0 : CUSTOM_CRS_VALIDATION QgsCoordinateReferenceSystem::customCrsValidation()
    1834                 :            : {
    1835                 :          0 :   return sCustomSrsValidation;
    1836                 :            : }
    1837                 :            : 
    1838                 :          0 : void QgsCoordinateReferenceSystem::debugPrint()
    1839                 :            : {
    1840                 :          0 :   QgsDebugMsg( QStringLiteral( "***SpatialRefSystem***" ) );
    1841                 :          0 :   QgsDebugMsg( "* Valid : " + ( d->mIsValid ? QString( "true" ) : QString( "false" ) ) );
    1842                 :          0 :   QgsDebugMsg( "* SrsId : " + QString::number( d->mSrsId ) );
    1843                 :          0 :   QgsDebugMsg( "* Proj4 : " + toProj() );
    1844                 :          0 :   QgsDebugMsg( "* WKT   : " + toWkt( WKT_PREFERRED ) );
    1845                 :          0 :   QgsDebugMsg( "* Desc. : " + d->mDescription );
    1846                 :          0 :   if ( mapUnits() == QgsUnitTypes::DistanceMeters )
    1847                 :            :   {
    1848                 :          0 :     QgsDebugMsg( QStringLiteral( "* Units : meters" ) );
    1849                 :          0 :   }
    1850                 :          0 :   else if ( mapUnits() == QgsUnitTypes::DistanceFeet )
    1851                 :            :   {
    1852                 :          0 :     QgsDebugMsg( QStringLiteral( "* Units : feet" ) );
    1853                 :          0 :   }
    1854                 :          0 :   else if ( mapUnits() == QgsUnitTypes::DistanceDegrees )
    1855                 :            :   {
    1856                 :          0 :     QgsDebugMsg( QStringLiteral( "* Units : degrees" ) );
    1857                 :          0 :   }
    1858                 :          0 : }
    1859                 :            : 
    1860                 :         63 : void QgsCoordinateReferenceSystem::setValidationHint( const QString &html )
    1861                 :            : {
    1862                 :         63 :   mValidationHint = html;
    1863                 :         63 : }
    1864                 :            : 
    1865                 :          0 : QString QgsCoordinateReferenceSystem::validationHint()
    1866                 :            : {
    1867                 :          0 :   return mValidationHint;
    1868                 :            : }
    1869                 :            : 
    1870                 :          0 : long QgsCoordinateReferenceSystem::saveAsUserCrs( const QString &name, Format nativeFormat )
    1871                 :            : {
    1872                 :          0 :   return QgsApplication::coordinateReferenceSystemRegistry()->addUserCrs( *this, name, nativeFormat );
    1873                 :            : }
    1874                 :            : 
    1875                 :          0 : long QgsCoordinateReferenceSystem::getRecordCount()
    1876                 :            : {
    1877                 :          0 :   sqlite3_database_unique_ptr database;
    1878                 :          0 :   sqlite3_statement_unique_ptr statement;
    1879                 :            :   int           myResult;
    1880                 :          0 :   long          myRecordCount = 0;
    1881                 :            :   //check the db is available
    1882                 :          0 :   myResult = database.open_v2( QgsApplication::qgisUserDatabaseFilePath(), SQLITE_OPEN_READONLY, nullptr );
    1883                 :          0 :   if ( myResult != SQLITE_OK )
    1884                 :            :   {
    1885                 :          0 :     QgsDebugMsg( QStringLiteral( "Can't open database: %1" ).arg( database.errorMessage() ) );
    1886                 :          0 :     return 0;
    1887                 :            :   }
    1888                 :            :   // Set up the query to retrieve the projection information needed to populate the ELLIPSOID list
    1889                 :          0 :   QString mySql = QStringLiteral( "select count(*) from tbl_srs" );
    1890                 :          0 :   statement = database.prepare( mySql, myResult );
    1891                 :          0 :   if ( myResult == SQLITE_OK )
    1892                 :            :   {
    1893                 :          0 :     if ( statement.step() == SQLITE_ROW )
    1894                 :            :     {
    1895                 :          0 :       QString myRecordCountString = statement.columnAsText( 0 );
    1896                 :          0 :       myRecordCount = myRecordCountString.toLong();
    1897                 :          0 :     }
    1898                 :          0 :   }
    1899                 :          0 :   return myRecordCount;
    1900                 :          0 : }
    1901                 :            : 
    1902                 :         10 : bool testIsGeographic( PJ *crs )
    1903                 :            : {
    1904                 :         10 :   PJ_CONTEXT *pjContext = QgsProjContext::get();
    1905                 :         10 :   bool isGeographic = false;
    1906                 :         10 :   QgsProjUtils::proj_pj_unique_ptr coordinateSystem( proj_crs_get_coordinate_system( pjContext, crs ) );
    1907                 :         10 :   if ( coordinateSystem )
    1908                 :            :   {
    1909                 :         10 :     const int axisCount = proj_cs_get_axis_count( pjContext, coordinateSystem.get() );
    1910                 :         10 :     if ( axisCount > 0 )
    1911                 :            :     {
    1912                 :         10 :       const char *outUnitAuthName = nullptr;
    1913                 :         10 :       const char *outUnitAuthCode = nullptr;
    1914                 :            :       // Read only first axis
    1915                 :         10 :       proj_cs_get_axis_info( pjContext, coordinateSystem.get(), 0,
    1916                 :            :                              nullptr,
    1917                 :            :                              nullptr,
    1918                 :            :                              nullptr,
    1919                 :            :                              nullptr,
    1920                 :            :                              nullptr,
    1921                 :            :                              &outUnitAuthName,
    1922                 :            :                              &outUnitAuthCode );
    1923                 :            : 
    1924                 :         10 :       if ( outUnitAuthName && outUnitAuthCode )
    1925                 :            :       {
    1926                 :         10 :         const char *unitCategory = nullptr;
    1927                 :         10 :         if ( proj_uom_get_info_from_database( pjContext, outUnitAuthName, outUnitAuthCode, nullptr, nullptr, &unitCategory ) )
    1928                 :            :         {
    1929                 :         10 :           isGeographic = QString( unitCategory ).compare( QLatin1String( "angular" ), Qt::CaseInsensitive ) == 0;
    1930                 :         10 :         }
    1931                 :         10 :       }
    1932                 :         10 :     }
    1933                 :         10 :   }
    1934                 :         10 :   return isGeographic;
    1935                 :         10 : }
    1936                 :            : 
    1937                 :         10 : void getOperationAndEllipsoidFromProjString( const QString &proj, QString &operation, QString &ellipsoid )
    1938                 :            : {
    1939                 :         13 :   thread_local const QRegularExpression projRegExp( QStringLiteral( "\\+proj=(\\S+)" ) );
    1940                 :         10 :   const QRegularExpressionMatch projMatch = projRegExp.match( proj );
    1941                 :         10 :   if ( !projMatch.hasMatch() )
    1942                 :            :   {
    1943                 :          0 :     QgsDebugMsgLevel( QStringLiteral( "no +proj argument found [%2]" ).arg( proj ), 2 );
    1944                 :          0 :     return;
    1945                 :            :   }
    1946                 :         10 :   operation = projMatch.captured( 1 );
    1947                 :            : 
    1948                 :         13 :   thread_local const QRegularExpression ellipseRegExp( QStringLiteral( "\\+(?:ellps|datum)=(\\S+)" ) );
    1949                 :         10 :   const QRegularExpressionMatch ellipseMatch = projRegExp.match( proj );
    1950                 :         10 :   QString ellps;
    1951                 :         10 :   if ( !ellipseMatch.hasMatch() )
    1952                 :            :   {
    1953                 :          0 :     ellipsoid = ellipseMatch.captured( 1 );
    1954                 :          0 :   }
    1955                 :            :   else
    1956                 :            :   {
    1957                 :            :     // satisfy not null constraint on ellipsoid_acronym field
    1958                 :            :     // possibly we should drop the constraint, yet the crses with missing ellipsoid_acronym are malformed
    1959                 :            :     // and will result in oddities within other areas of QGIS (e.g. project ellipsoid won't be correctly
    1960                 :            :     // set for these CRSes). Better just hack around and make the constraint happy for now,
    1961                 :            :     // and hope that the definitions get corrected in future.
    1962                 :         10 :     ellipsoid.clear();
    1963                 :            :   }
    1964                 :         10 : }
    1965                 :            : 
    1966                 :            : 
    1967                 :         10 : bool QgsCoordinateReferenceSystem::loadFromAuthCode( const QString &auth, const QString &code )
    1968                 :            : {
    1969                 :         10 :   d.detach();
    1970                 :         10 :   d->mIsValid = false;
    1971                 :         10 :   d->mWktPreferred.clear();
    1972                 :            : 
    1973                 :         10 :   PJ_CONTEXT *pjContext = QgsProjContext::get();
    1974                 :         10 :   QgsProjUtils::proj_pj_unique_ptr crs( proj_create_from_database( pjContext, auth.toUtf8().constData(), code.toUtf8().constData(), PJ_CATEGORY_CRS, false, nullptr ) );
    1975                 :         10 :   if ( !crs )
    1976                 :            :   {
    1977                 :          0 :     return false;
    1978                 :            :   }
    1979                 :            : 
    1980                 :         10 :   switch ( proj_get_type( crs.get() ) )
    1981                 :            :   {
    1982                 :            :     case PJ_TYPE_VERTICAL_CRS:
    1983                 :          0 :       return false;
    1984                 :            : 
    1985                 :            :     default:
    1986                 :         10 :       break;
    1987                 :            :   }
    1988                 :            : 
    1989                 :         10 :   crs = QgsProjUtils::crsToSingleCrs( crs.get() );
    1990                 :            : 
    1991                 :         10 :   QString proj4 = getFullProjString( crs.get() );
    1992                 :         10 :   proj4.replace( QLatin1String( "+type=crs" ), QString() );
    1993                 :         10 :   proj4 = proj4.trimmed();
    1994                 :            : 
    1995                 :         10 :   d->mIsValid = true;
    1996                 :         10 :   d->mProj4 = proj4;
    1997                 :         10 :   d->mWktPreferred.clear();
    1998                 :         10 :   d->mDescription = QString( proj_get_name( crs.get() ) );
    1999                 :         20 :   d->mAuthId = QStringLiteral( "%1:%2" ).arg( auth, code );
    2000                 :         10 :   d->mIsGeographic = testIsGeographic( crs.get() );
    2001                 :         10 :   d->mAxisInvertedDirty = true;
    2002                 :         10 :   QString operation;
    2003                 :         10 :   QString ellipsoid;
    2004                 :         10 :   getOperationAndEllipsoidFromProjString( proj4, operation, ellipsoid );
    2005                 :         10 :   d->mProjectionAcronym = operation;
    2006                 :         10 :   d->mEllipsoidAcronym.clear();
    2007                 :         10 :   d->setPj( std::move( crs ) );
    2008                 :            : 
    2009                 :         20 :   const QString dbVals = sAuthIdToQgisSrsIdMap.value( QStringLiteral( "%1:%2" ).arg( auth, code ).toUpper() );
    2010                 :         10 :   QString srsId;
    2011                 :         10 :   QString srId;
    2012                 :         10 :   if ( !dbVals.isEmpty() )
    2013                 :            :   {
    2014                 :         10 :     const QStringList parts = dbVals.split( ',' );
    2015                 :         10 :     d->mSrsId = parts.at( 0 ).toInt();
    2016                 :         10 :     d->mSRID = parts.at( 1 ).toInt();
    2017                 :         10 :   }
    2018                 :            : 
    2019                 :         10 :   setMapUnits();
    2020                 :            : 
    2021                 :         10 :   return true;
    2022                 :         10 : }
    2023                 :            : 
    2024                 :          0 : QList<long> QgsCoordinateReferenceSystem::userSrsIds()
    2025                 :            : {
    2026                 :          0 :   QList<long> results;
    2027                 :            :   // check user defined projection database
    2028                 :          0 :   const QString db = QgsApplication::qgisUserDatabaseFilePath();
    2029                 :            : 
    2030                 :          0 :   QFileInfo myInfo( db );
    2031                 :          0 :   if ( !myInfo.exists() )
    2032                 :            :   {
    2033                 :          0 :     QgsDebugMsg( "failed : " + db + " does not exist!" );
    2034                 :          0 :     return results;
    2035                 :            :   }
    2036                 :            : 
    2037                 :          0 :   sqlite3_database_unique_ptr database;
    2038                 :          0 :   sqlite3_statement_unique_ptr statement;
    2039                 :            : 
    2040                 :            :   //check the db is available
    2041                 :          0 :   int result = openDatabase( db, database );
    2042                 :          0 :   if ( result != SQLITE_OK )
    2043                 :            :   {
    2044                 :          0 :     QgsDebugMsg( "failed : " + db + " could not be opened!" );
    2045                 :          0 :     return results;
    2046                 :            :   }
    2047                 :            : 
    2048                 :          0 :   QString sql = QStringLiteral( "select srs_id from tbl_srs where srs_id >= %1" ).arg( USER_CRS_START_ID );
    2049                 :            :   int rc;
    2050                 :          0 :   statement = database.prepare( sql, rc );
    2051                 :          0 :   while ( true )
    2052                 :            :   {
    2053                 :          0 :     int ret = statement.step();
    2054                 :            : 
    2055                 :          0 :     if ( ret == SQLITE_DONE )
    2056                 :            :     {
    2057                 :            :       // there are no more rows to fetch - we can stop looping
    2058                 :          0 :       break;
    2059                 :            :     }
    2060                 :            : 
    2061                 :          0 :     if ( ret == SQLITE_ROW )
    2062                 :            :     {
    2063                 :          0 :       results.append( statement.columnAsInt64( 0 ) );
    2064                 :          0 :     }
    2065                 :            :     else
    2066                 :            :     {
    2067                 :          0 :       QgsMessageLog::logMessage( QObject::tr( "SQLite error: %2\nSQL: %1" ).arg( sql, sqlite3_errmsg( database.get() ) ), QObject::tr( "SpatiaLite" ) );
    2068                 :          0 :       break;
    2069                 :            :     }
    2070                 :            :   }
    2071                 :            : 
    2072                 :          0 :   return results;
    2073                 :          0 : }
    2074                 :            : 
    2075                 :          0 : long QgsCoordinateReferenceSystem::matchToUserCrs() const
    2076                 :            : {
    2077                 :          0 :   PJ *obj = d->threadLocalProjObject();
    2078                 :          0 :   if ( !obj )
    2079                 :          0 :     return 0;
    2080                 :            : 
    2081                 :          0 :   const QList< long > ids = userSrsIds();
    2082                 :          0 :   for ( long id : ids )
    2083                 :            :   {
    2084                 :          0 :     QgsCoordinateReferenceSystem candidate = QgsCoordinateReferenceSystem::fromSrsId( id );
    2085                 :          0 :     if ( candidate.projObject() && proj_is_equivalent_to( obj, candidate.projObject(), PJ_COMP_EQUIVALENT ) )
    2086                 :            :     {
    2087                 :          0 :       return id;
    2088                 :            :     }
    2089                 :          0 :   }
    2090                 :          0 :   return 0;
    2091                 :          0 : }
    2092                 :            : 
    2093                 :          0 : static void sync_db_proj_logger( void * /* user_data */, int level, const char *message )
    2094                 :            : {
    2095                 :            : #ifndef QGISDEBUG
    2096                 :            :   Q_UNUSED( message )
    2097                 :            : #endif
    2098                 :          0 :   if ( level == PJ_LOG_ERROR )
    2099                 :            :   {
    2100                 :          0 :     QgsDebugMsgLevel( QStringLiteral( "PROJ: %1" ).arg( message ), 2 );
    2101                 :          0 :   }
    2102                 :          0 :   else if ( level == PJ_LOG_DEBUG )
    2103                 :            :   {
    2104                 :          0 :     QgsDebugMsgLevel( QStringLiteral( "PROJ: %1" ).arg( message ), 3 );
    2105                 :          0 :   }
    2106                 :          0 : }
    2107                 :            : 
    2108                 :          0 : int QgsCoordinateReferenceSystem::syncDatabase()
    2109                 :            : {
    2110                 :          0 :   setlocale( LC_ALL, "C" );
    2111                 :          0 :   QString dbFilePath = QgsApplication::srsDatabaseFilePath();
    2112                 :            : 
    2113                 :          0 :   int inserted = 0, updated = 0, deleted = 0, errors = 0;
    2114                 :            : 
    2115                 :          0 :   QgsDebugMsgLevel( QStringLiteral( "Load srs db from: %1" ).arg( QgsApplication::srsDatabaseFilePath().toLocal8Bit().constData() ), 4 );
    2116                 :            : 
    2117                 :          0 :   sqlite3_database_unique_ptr database;
    2118                 :          0 :   if ( database.open( dbFilePath ) != SQLITE_OK )
    2119                 :            :   {
    2120                 :          0 :     QgsDebugMsg( QStringLiteral( "Could not open database: %1 (%2)\n" ).arg( QgsApplication::srsDatabaseFilePath(), database.errorMessage() ) );
    2121                 :          0 :     return -1;
    2122                 :            :   }
    2123                 :            : 
    2124                 :          0 :   if ( sqlite3_exec( database.get(), "BEGIN TRANSACTION", nullptr, nullptr, nullptr ) != SQLITE_OK )
    2125                 :            :   {
    2126                 :          0 :     QgsDebugMsg( QStringLiteral( "Could not begin transaction: %1 (%2)\n" ).arg( QgsApplication::srsDatabaseFilePath(), database.errorMessage() ) );
    2127                 :          0 :     return -1;
    2128                 :            :   }
    2129                 :            : 
    2130                 :          0 :   sqlite3_statement_unique_ptr statement;
    2131                 :            :   int result;
    2132                 :          0 :   char *errMsg = nullptr;
    2133                 :            : 
    2134                 :          0 :   if ( sqlite3_exec( database.get(), "create table tbl_info (proj_major INT, proj_minor INT, proj_patch INT)", nullptr, nullptr, nullptr ) == SQLITE_OK )
    2135                 :            :   {
    2136                 :          0 :     QString sql = QStringLiteral( "INSERT INTO tbl_info(proj_major, proj_minor, proj_patch) VALUES (%1, %2,%3)" )
    2137                 :          0 :                   .arg( QString::number( PROJ_VERSION_MAJOR ),
    2138                 :          0 :                         QString::number( PROJ_VERSION_MINOR ),
    2139                 :          0 :                         QString::number( PROJ_VERSION_PATCH ) );
    2140                 :          0 :     if ( sqlite3_exec( database.get(), sql.toUtf8(), nullptr, nullptr, &errMsg ) != SQLITE_OK )
    2141                 :            :     {
    2142                 :          0 :       QgsDebugMsg( QStringLiteral( "Could not execute: %1 [%2/%3]\n" ).arg(
    2143                 :            :                      sql,
    2144                 :            :                      database.errorMessage(),
    2145                 :            :                      errMsg ? errMsg : "(unknown error)" ) );
    2146                 :          0 :       if ( errMsg )
    2147                 :          0 :         sqlite3_free( errMsg );
    2148                 :          0 :       return -1;
    2149                 :            :     }
    2150                 :          0 :   }
    2151                 :            :   else
    2152                 :            :   {
    2153                 :            :     // retrieve last update details
    2154                 :          0 :     QString sql = QStringLiteral( "SELECT proj_major, proj_minor, proj_patch FROM tbl_info" );
    2155                 :          0 :     statement = database.prepare( sql, result );
    2156                 :          0 :     if ( result != SQLITE_OK )
    2157                 :            :     {
    2158                 :          0 :       QgsDebugMsg( QStringLiteral( "Could not prepare: %1 [%2]\n" ).arg( sql, database.errorMessage() ) );
    2159                 :          0 :       return -1;
    2160                 :            :     }
    2161                 :          0 :     if ( statement.step() == SQLITE_ROW )
    2162                 :            :     {
    2163                 :          0 :       int major = statement.columnAsInt64( 0 );
    2164                 :          0 :       int minor = statement.columnAsInt64( 1 );
    2165                 :          0 :       int patch = statement.columnAsInt64( 2 );
    2166                 :          0 :       if ( major == PROJ_VERSION_MAJOR && minor == PROJ_VERSION_MINOR && patch == PROJ_VERSION_PATCH )
    2167                 :            :         // yay, nothing to do!
    2168                 :          0 :         return 0;
    2169                 :          0 :     }
    2170                 :            :     else
    2171                 :            :     {
    2172                 :          0 :       QgsDebugMsg( QStringLiteral( "Could not retrieve previous CRS sync PROJ version number" ) );
    2173                 :          0 :       return -1;
    2174                 :            :     }
    2175                 :          0 :   }
    2176                 :            : 
    2177                 :          0 :   PJ_CONTEXT *pjContext = QgsProjContext::get();
    2178                 :            :   // silence proj warnings
    2179                 :          0 :   proj_log_func( pjContext, nullptr, sync_db_proj_logger );
    2180                 :            : 
    2181                 :          0 :   PROJ_STRING_LIST authorities = proj_get_authorities_from_database( pjContext );
    2182                 :            : 
    2183                 :          0 :   int nextSrsId = 63321;
    2184                 :          0 :   int nextSrId = 520003321;
    2185                 :          0 :   for ( auto authIter = authorities; authIter && *authIter; ++authIter )
    2186                 :            :   {
    2187                 :          0 :     const QString authority( *authIter );
    2188                 :          0 :     QgsDebugMsgLevel( QStringLiteral( "Loading authority '%1'" ).arg( authority ), 2 );
    2189                 :          0 :     PROJ_STRING_LIST codes = proj_get_codes_from_database( pjContext, *authIter, PJ_TYPE_CRS, true );
    2190                 :            : 
    2191                 :          0 :     QStringList allCodes;
    2192                 :            : 
    2193                 :          0 :     for ( auto codesIter = codes; codesIter && *codesIter; ++codesIter )
    2194                 :            :     {
    2195                 :          0 :       const QString code( *codesIter );
    2196                 :          0 :       allCodes << QgsSqliteUtils::quotedString( code );
    2197                 :          0 :       QgsDebugMsgLevel( QStringLiteral( "Loading code '%1'" ).arg( code ), 4 );
    2198                 :          0 :       QgsProjUtils::proj_pj_unique_ptr crs( proj_create_from_database( pjContext, *authIter, *codesIter, PJ_CATEGORY_CRS, false, nullptr ) );
    2199                 :          0 :       if ( !crs )
    2200                 :            :       {
    2201                 :          0 :         QgsDebugMsg( QStringLiteral( "Could not load '%1:%2'" ).arg( authority, code ) );
    2202                 :          0 :         continue;
    2203                 :            :       }
    2204                 :            : 
    2205                 :          0 :       switch ( proj_get_type( crs.get() ) )
    2206                 :            :       {
    2207                 :            :         case PJ_TYPE_VERTICAL_CRS: // don't need these in the CRS db
    2208                 :          0 :           continue;
    2209                 :            : 
    2210                 :            :         default:
    2211                 :          0 :           break;
    2212                 :            :       }
    2213                 :            : 
    2214                 :          0 :       crs = QgsProjUtils::crsToSingleCrs( crs.get() );
    2215                 :            : 
    2216                 :          0 :       QString proj4 = getFullProjString( crs.get() );
    2217                 :          0 :       proj4.replace( QLatin1String( "+type=crs" ), QString() );
    2218                 :          0 :       proj4 = proj4.trimmed();
    2219                 :            : 
    2220                 :          0 :       if ( proj4.isEmpty() )
    2221                 :            :       {
    2222                 :          0 :         QgsDebugMsgLevel( QStringLiteral( "No proj4 for '%1:%2'" ).arg( authority, code ), 2 );
    2223                 :            :         // satisfy not null constraint
    2224                 :          0 :         proj4 = "";
    2225                 :          0 :       }
    2226                 :            : 
    2227                 :          0 :       const bool deprecated = proj_is_deprecated( crs.get() );
    2228                 :          0 :       const QString name( proj_get_name( crs.get() ) );
    2229                 :            : 
    2230                 :          0 :       QString sql = QStringLiteral( "SELECT parameters,description,deprecated FROM tbl_srs WHERE auth_name='%1' AND auth_id='%2'" ).arg( authority, code );
    2231                 :          0 :       statement = database.prepare( sql, result );
    2232                 :          0 :       if ( result != SQLITE_OK )
    2233                 :            :       {
    2234                 :          0 :         QgsDebugMsg( QStringLiteral( "Could not prepare: %1 [%2]\n" ).arg( sql, database.errorMessage() ) );
    2235                 :          0 :         continue;
    2236                 :            :       }
    2237                 :            : 
    2238                 :          0 :       QString srsProj4;
    2239                 :          0 :       QString srsDesc;
    2240                 :          0 :       bool srsDeprecated = deprecated;
    2241                 :          0 :       if ( statement.step() == SQLITE_ROW )
    2242                 :            :       {
    2243                 :          0 :         srsProj4 = statement.columnAsText( 0 );
    2244                 :          0 :         srsDesc = statement.columnAsText( 1 );
    2245                 :          0 :         srsDeprecated = statement.columnAsText( 2 ).toInt() != 0;
    2246                 :          0 :       }
    2247                 :            : 
    2248                 :          0 :       if ( !srsProj4.isEmpty() || !srsDesc.isEmpty() )
    2249                 :            :       {
    2250                 :          0 :         if ( proj4 != srsProj4 || name != srsDesc || deprecated != srsDeprecated )
    2251                 :            :         {
    2252                 :          0 :           errMsg = nullptr;
    2253                 :          0 :           sql = QStringLiteral( "UPDATE tbl_srs SET parameters=%1,description=%2,deprecated=%3 WHERE auth_name=%4 AND auth_id=%5" )
    2254                 :          0 :                 .arg( QgsSqliteUtils::quotedString( proj4 ) )
    2255                 :          0 :                 .arg( QgsSqliteUtils::quotedString( name ) )
    2256                 :          0 :                 .arg( deprecated ? 1 : 0 )
    2257                 :          0 :                 .arg( QgsSqliteUtils::quotedString( authority ), QgsSqliteUtils::quotedString( code ) );
    2258                 :            : 
    2259                 :          0 :           if ( sqlite3_exec( database.get(), sql.toUtf8(), nullptr, nullptr, &errMsg ) != SQLITE_OK )
    2260                 :            :           {
    2261                 :          0 :             QgsDebugMsg( QStringLiteral( "Could not execute: %1 [%2/%3]\n" ).arg(
    2262                 :            :                            sql,
    2263                 :            :                            database.errorMessage(),
    2264                 :            :                            errMsg ? errMsg : "(unknown error)" ) );
    2265                 :          0 :             if ( errMsg )
    2266                 :          0 :               sqlite3_free( errMsg );
    2267                 :          0 :             errors++;
    2268                 :          0 :           }
    2269                 :            :           else
    2270                 :            :           {
    2271                 :          0 :             updated++;
    2272                 :            :           }
    2273                 :          0 :         }
    2274                 :          0 :       }
    2275                 :            :       else
    2276                 :            :       {
    2277                 :            :         // there's a not-null contraint on these columns, so we must use empty strings instead
    2278                 :          0 :         QString operation = "";
    2279                 :          0 :         QString ellps = "";
    2280                 :          0 :         getOperationAndEllipsoidFromProjString( proj4, operation, ellps );
    2281                 :          0 :         const bool isGeographic = testIsGeographic( crs.get() );
    2282                 :            : 
    2283                 :            :         // work out srid and srsid
    2284                 :          0 :         const QString dbVals = sAuthIdToQgisSrsIdMap.value( QStringLiteral( "%1:%2" ).arg( authority, code ) );
    2285                 :          0 :         QString srsId;
    2286                 :          0 :         QString srId;
    2287                 :          0 :         if ( !dbVals.isEmpty() )
    2288                 :            :         {
    2289                 :          0 :           const QStringList parts = dbVals.split( ',' );
    2290                 :          0 :           srsId = parts.at( 0 );
    2291                 :          0 :           srId = parts.at( 1 );
    2292                 :          0 :         }
    2293                 :          0 :         if ( srId.isEmpty() )
    2294                 :            :         {
    2295                 :          0 :           srId = QString::number( nextSrId );
    2296                 :          0 :           nextSrId++;
    2297                 :          0 :         }
    2298                 :          0 :         if ( srsId.isEmpty() )
    2299                 :            :         {
    2300                 :          0 :           srsId = QString::number( nextSrsId );
    2301                 :          0 :           nextSrsId++;
    2302                 :          0 :         }
    2303                 :            : 
    2304                 :          0 :         if ( !srsId.isEmpty() )
    2305                 :            :         {
    2306                 :          0 :           sql = QStringLiteral( "INSERT INTO tbl_srs(srs_id, description,projection_acronym,ellipsoid_acronym,parameters,srid,auth_name,auth_id,is_geo,deprecated) VALUES (%1, %2,%3,%4,%5,%6,%7,%8,%9,%10)" )
    2307                 :          0 :                 .arg( srsId )
    2308                 :          0 :                 .arg( QgsSqliteUtils::quotedString( name ),
    2309                 :          0 :                       QgsSqliteUtils::quotedString( operation ),
    2310                 :          0 :                       QgsSqliteUtils::quotedString( ellps ),
    2311                 :          0 :                       QgsSqliteUtils::quotedString( proj4 ) )
    2312                 :          0 :                 .arg( srId )
    2313                 :          0 :                 .arg( QgsSqliteUtils::quotedString( authority ) )
    2314                 :          0 :                 .arg( QgsSqliteUtils::quotedString( code ) )
    2315                 :          0 :                 .arg( isGeographic ? 1 : 0 )
    2316                 :          0 :                 .arg( deprecated ? 1 : 0 );
    2317                 :          0 :         }
    2318                 :            :         else
    2319                 :            :         {
    2320                 :          0 :           sql = QStringLiteral( "INSERT INTO tbl_srs(description,projection_acronym,ellipsoid_acronym,parameters,srid,auth_name,auth_id,is_geo,deprecated) VALUES (%2,%3,%4,%5,%6,%7,%8,%9,%10)" )
    2321                 :          0 :                 .arg( QgsSqliteUtils::quotedString( name ),
    2322                 :          0 :                       QgsSqliteUtils::quotedString( operation ),
    2323                 :          0 :                       QgsSqliteUtils::quotedString( ellps ),
    2324                 :          0 :                       QgsSqliteUtils::quotedString( proj4 ) )
    2325                 :          0 :                 .arg( srId )
    2326                 :          0 :                 .arg( QgsSqliteUtils::quotedString( authority ) )
    2327                 :          0 :                 .arg( QgsSqliteUtils::quotedString( code ) )
    2328                 :          0 :                 .arg( isGeographic ? 1 : 0 )
    2329                 :          0 :                 .arg( deprecated ? 1 : 0 );
    2330                 :            :         }
    2331                 :            : 
    2332                 :          0 :         errMsg = nullptr;
    2333                 :          0 :         if ( sqlite3_exec( database.get(), sql.toUtf8(), nullptr, nullptr, &errMsg ) == SQLITE_OK )
    2334                 :            :         {
    2335                 :          0 :           inserted++;
    2336                 :          0 :         }
    2337                 :            :         else
    2338                 :            :         {
    2339                 :          0 :           qCritical( "Could not execute: %s [%s/%s]\n",
    2340                 :          0 :                      sql.toLocal8Bit().constData(),
    2341                 :          0 :                      sqlite3_errmsg( database.get() ),
    2342                 :          0 :                      errMsg ? errMsg : "(unknown error)" );
    2343                 :          0 :           errors++;
    2344                 :            : 
    2345                 :          0 :           if ( errMsg )
    2346                 :          0 :             sqlite3_free( errMsg );
    2347                 :            :         }
    2348                 :          0 :       }
    2349                 :          0 :     }
    2350                 :            : 
    2351                 :          0 :     proj_string_list_destroy( codes );
    2352                 :            : 
    2353                 :          0 :     const QString sql = QStringLiteral( "DELETE FROM tbl_srs WHERE auth_name='%1' AND NOT auth_id IN (%2)" ).arg( authority, allCodes.join( ',' ) );
    2354                 :          0 :     if ( sqlite3_exec( database.get(), sql.toUtf8(), nullptr, nullptr, nullptr ) == SQLITE_OK )
    2355                 :            :     {
    2356                 :          0 :       deleted = sqlite3_changes( database.get() );
    2357                 :          0 :     }
    2358                 :            :     else
    2359                 :            :     {
    2360                 :          0 :       errors++;
    2361                 :          0 :       qCritical( "Could not execute: %s [%s]\n",
    2362                 :          0 :                  sql.toLocal8Bit().constData(),
    2363                 :          0 :                  sqlite3_errmsg( database.get() ) );
    2364                 :            :     }
    2365                 :            : 
    2366                 :          0 :   }
    2367                 :          0 :   proj_string_list_destroy( authorities );
    2368                 :            : 
    2369                 :          0 :   QString sql = QStringLiteral( "UPDATE tbl_info set proj_major=%1,proj_minor=%2,proj_patch=%3" )
    2370                 :          0 :                 .arg( QString::number( PROJ_VERSION_MAJOR ),
    2371                 :          0 :                       QString::number( PROJ_VERSION_MINOR ),
    2372                 :          0 :                       QString::number( PROJ_VERSION_PATCH ) );
    2373                 :          0 :   if ( sqlite3_exec( database.get(), sql.toUtf8(), nullptr, nullptr, &errMsg ) != SQLITE_OK )
    2374                 :            :   {
    2375                 :          0 :     QgsDebugMsg( QStringLiteral( "Could not execute: %1 [%2/%3]\n" ).arg(
    2376                 :            :                    sql,
    2377                 :            :                    database.errorMessage(),
    2378                 :            :                    errMsg ? errMsg : "(unknown error)" ) );
    2379                 :          0 :     if ( errMsg )
    2380                 :          0 :       sqlite3_free( errMsg );
    2381                 :          0 :     return -1;
    2382                 :            :   }
    2383                 :            : 
    2384                 :          0 :   if ( sqlite3_exec( database.get(), "COMMIT", nullptr, nullptr, nullptr ) != SQLITE_OK )
    2385                 :            :   {
    2386                 :          0 :     QgsDebugMsg( QStringLiteral( "Could not commit transaction: %1 [%2]\n" ).arg(
    2387                 :            :                    QgsApplication::srsDatabaseFilePath(),
    2388                 :            :                    sqlite3_errmsg( database.get() ) )
    2389                 :            :                );
    2390                 :          0 :     return -1;
    2391                 :            :   }
    2392                 :            : 
    2393                 :            : #ifdef QGISDEBUG
    2394                 :            :   QgsDebugMsgLevel( QStringLiteral( "CRS update (inserted:%1 updated:%2 deleted:%3 errors:%4)" ).arg( inserted ).arg( updated ).arg( deleted ).arg( errors ), 4 );
    2395                 :            : #else
    2396                 :            :   Q_UNUSED( deleted )
    2397                 :            : #endif
    2398                 :            : 
    2399                 :          0 :   if ( errors > 0 )
    2400                 :          0 :     return -errors;
    2401                 :            :   else
    2402                 :          0 :     return updated + inserted;
    2403                 :          0 : }
    2404                 :            : 
    2405                 :          0 : const QHash<QString, QgsCoordinateReferenceSystem> &QgsCoordinateReferenceSystem::stringCache()
    2406                 :            : {
    2407                 :          0 :   return *sStringCache();
    2408                 :            : }
    2409                 :            : 
    2410                 :          0 : const QHash<QString, QgsCoordinateReferenceSystem> &QgsCoordinateReferenceSystem::projCache()
    2411                 :            : {
    2412                 :          0 :   return *sProj4Cache();
    2413                 :            : }
    2414                 :            : 
    2415                 :          0 : const QHash<QString, QgsCoordinateReferenceSystem> &QgsCoordinateReferenceSystem::ogcCache()
    2416                 :            : {
    2417                 :          0 :   return *sOgcCache();
    2418                 :            : }
    2419                 :            : 
    2420                 :          0 : const QHash<QString, QgsCoordinateReferenceSystem> &QgsCoordinateReferenceSystem::wktCache()
    2421                 :            : {
    2422                 :          0 :   return *sWktCache();
    2423                 :            : }
    2424                 :            : 
    2425                 :          0 : const QHash<long, QgsCoordinateReferenceSystem> &QgsCoordinateReferenceSystem::srIdCache()
    2426                 :            : {
    2427                 :          0 :   return *sSrIdCache();
    2428                 :            : }
    2429                 :            : 
    2430                 :          0 : const QHash<long, QgsCoordinateReferenceSystem> &QgsCoordinateReferenceSystem::srsIdCache()
    2431                 :            : {
    2432                 :          0 :   return *sSrsIdCache();
    2433                 :            : }
    2434                 :            : 
    2435                 :          0 : QString QgsCoordinateReferenceSystem::geographicCrsAuthId() const
    2436                 :            : {
    2437                 :          0 :   if ( isGeographic() )
    2438                 :            :   {
    2439                 :          0 :     return d->mAuthId;
    2440                 :            :   }
    2441                 :          0 :   else if ( PJ *obj = d->threadLocalProjObject() )
    2442                 :            :   {
    2443                 :          0 :     QgsProjUtils::proj_pj_unique_ptr geoCrs( proj_crs_get_geodetic_crs( QgsProjContext::get(), obj ) );
    2444                 :          0 :     return geoCrs ? QStringLiteral( "%1:%2" ).arg( proj_get_id_auth_name( geoCrs.get(), 0 ), proj_get_id_code( geoCrs.get(), 0 ) ) : QString();
    2445                 :          0 :   }
    2446                 :            :   else
    2447                 :            :   {
    2448                 :          0 :     return QString();
    2449                 :            :   }
    2450                 :          0 : }
    2451                 :            : 
    2452                 :          4 : PJ *QgsCoordinateReferenceSystem::projObject() const
    2453                 :            : {
    2454                 :          4 :   return d->threadLocalProjObject();
    2455                 :            : }
    2456                 :            : 
    2457                 :          0 : QStringList QgsCoordinateReferenceSystem::recentProjections()
    2458                 :            : {
    2459                 :          0 :   QStringList projections;
    2460                 :          0 :   const QList<QgsCoordinateReferenceSystem> res = recentCoordinateReferenceSystems();
    2461                 :          0 :   projections.reserve( res.size() );
    2462                 :          0 :   for ( const QgsCoordinateReferenceSystem &crs : res )
    2463                 :            :   {
    2464                 :          0 :     projections << QString::number( crs.srsid() );
    2465                 :            :   }
    2466                 :          0 :   return projections;
    2467                 :          0 : }
    2468                 :            : 
    2469                 :          0 : QList<QgsCoordinateReferenceSystem> QgsCoordinateReferenceSystem::recentCoordinateReferenceSystems()
    2470                 :            : {
    2471                 :          0 :   QList<QgsCoordinateReferenceSystem> res;
    2472                 :            : 
    2473                 :            :   // Read settings from persistent storage
    2474                 :          0 :   QgsSettings settings;
    2475                 :          0 :   QStringList projectionsProj4  = settings.value( QStringLiteral( "UI/recentProjectionsProj4" ) ).toStringList();
    2476                 :          0 :   QStringList projectionsWkt = settings.value( QStringLiteral( "UI/recentProjectionsWkt" ) ).toStringList();
    2477                 :          0 :   QStringList projectionsAuthId = settings.value( QStringLiteral( "UI/recentProjectionsAuthId" ) ).toStringList();
    2478                 :          0 :   int max = std::max( projectionsAuthId.size(), std::max( projectionsProj4.size(), projectionsWkt.size() ) );
    2479                 :          0 :   res.reserve( max );
    2480                 :          0 :   for ( int i = 0; i < max; ++i )
    2481                 :            :   {
    2482                 :          0 :     const QString proj = projectionsProj4.value( i );
    2483                 :          0 :     const QString wkt = projectionsWkt.value( i );
    2484                 :          0 :     const QString authid = projectionsAuthId.value( i );
    2485                 :            : 
    2486                 :          0 :     QgsCoordinateReferenceSystem crs;
    2487                 :          0 :     if ( !authid.isEmpty() )
    2488                 :          0 :       crs = QgsCoordinateReferenceSystem( authid );
    2489                 :          0 :     if ( !crs.isValid() && !wkt.isEmpty() )
    2490                 :          0 :       crs.createFromWkt( wkt );
    2491                 :          0 :     if ( !crs.isValid() && !proj.isEmpty() )
    2492                 :          0 :       crs.createFromProj( wkt );
    2493                 :            : 
    2494                 :          0 :     if ( crs.isValid() )
    2495                 :          0 :       res << crs;
    2496                 :          0 :   }
    2497                 :          0 :   return res;
    2498                 :          0 : }
    2499                 :            : 
    2500                 :          0 : void QgsCoordinateReferenceSystem::pushRecentCoordinateReferenceSystem( const QgsCoordinateReferenceSystem &crs )
    2501                 :            : {
    2502                 :            :   // we only want saved and standard CRSes in the recent list
    2503                 :          0 :   if ( crs.srsid() == 0 || !crs.isValid() )
    2504                 :          0 :     return;
    2505                 :            : 
    2506                 :          0 :   QList<QgsCoordinateReferenceSystem> recent = recentCoordinateReferenceSystems();
    2507                 :          0 :   recent.removeAll( crs );
    2508                 :          0 :   recent.insert( 0, crs );
    2509                 :            : 
    2510                 :            :   // trim to max 30 items
    2511                 :          0 :   recent = recent.mid( 0, 30 );
    2512                 :          0 :   QStringList authids;
    2513                 :          0 :   authids.reserve( recent.size() );
    2514                 :          0 :   QStringList proj;
    2515                 :          0 :   proj.reserve( recent.size() );
    2516                 :          0 :   QStringList wkt;
    2517                 :          0 :   wkt.reserve( recent.size() );
    2518                 :          0 :   for ( const QgsCoordinateReferenceSystem &c : std::as_const( recent ) )
    2519                 :            :   {
    2520                 :          0 :     authids << c.authid();
    2521                 :          0 :     proj << c.toProj();
    2522                 :          0 :     wkt << c.toWkt( WKT_PREFERRED );
    2523                 :            :   }
    2524                 :            : 
    2525                 :          0 :   QgsSettings settings;
    2526                 :          0 :   settings.setValue( QStringLiteral( "UI/recentProjectionsAuthId" ), authids );
    2527                 :          0 :   settings.setValue( QStringLiteral( "UI/recentProjectionsWkt" ), wkt );
    2528                 :          0 :   settings.setValue( QStringLiteral( "UI/recentProjectionsProj4" ), proj );
    2529                 :          0 : }
    2530                 :            : 
    2531                 :          8 : void QgsCoordinateReferenceSystem::invalidateCache( bool disableCache )
    2532                 :            : {
    2533                 :          8 :   sSrIdCacheLock()->lockForWrite();
    2534                 :          8 :   if ( !sDisableSrIdCache )
    2535                 :            :   {
    2536                 :          5 :     if ( disableCache )
    2537                 :          5 :       sDisableSrIdCache = true;
    2538                 :          5 :     sSrIdCache()->clear();
    2539                 :          5 :   }
    2540                 :          8 :   sSrIdCacheLock()->unlock();
    2541                 :            : 
    2542                 :          8 :   sOgcLock()->lockForWrite();
    2543                 :          8 :   if ( !sDisableOgcCache )
    2544                 :            :   {
    2545                 :          5 :     if ( disableCache )
    2546                 :          5 :       sDisableOgcCache = true;
    2547                 :          5 :     sOgcCache()->clear();
    2548                 :          5 :   }
    2549                 :          8 :   sOgcLock()->unlock();
    2550                 :            : 
    2551                 :          8 :   sProj4CacheLock()->lockForWrite();
    2552                 :          8 :   if ( !sDisableProjCache )
    2553                 :            :   {
    2554                 :          5 :     if ( disableCache )
    2555                 :          5 :       sDisableProjCache = true;
    2556                 :          5 :     sProj4Cache()->clear();
    2557                 :          5 :   }
    2558                 :          8 :   sProj4CacheLock()->unlock();
    2559                 :            : 
    2560                 :          8 :   sCRSWktLock()->lockForWrite();
    2561                 :          8 :   if ( !sDisableWktCache )
    2562                 :            :   {
    2563                 :          5 :     if ( disableCache )
    2564                 :          5 :       sDisableWktCache = true;
    2565                 :          5 :     sWktCache()->clear();
    2566                 :          5 :   }
    2567                 :          8 :   sCRSWktLock()->unlock();
    2568                 :            : 
    2569                 :          8 :   sCRSSrsIdLock()->lockForWrite();
    2570                 :          8 :   if ( !sDisableSrsIdCache )
    2571                 :            :   {
    2572                 :          5 :     if ( disableCache )
    2573                 :          5 :       sDisableSrsIdCache = true;
    2574                 :          5 :     sSrsIdCache()->clear();
    2575                 :          5 :   }
    2576                 :          8 :   sCRSSrsIdLock()->unlock();
    2577                 :            : 
    2578                 :          8 :   sCrsStringLock()->lockForWrite();
    2579                 :          8 :   if ( !sDisableStringCache )
    2580                 :            :   {
    2581                 :          5 :     if ( disableCache )
    2582                 :          5 :       sDisableStringCache = true;
    2583                 :          5 :     sStringCache()->clear();
    2584                 :          5 :   }
    2585                 :          8 :   sCrsStringLock()->unlock();
    2586                 :          8 : }

Generated by: LCOV version 1.14