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