QGIS API Documentation  3.37.0-Master (a5b4d9743e8)
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 "qgsogcutils.h"
44 #include "qgsprojectionfactors.h"
45 #include "qgsprojoperation.h"
47 
48 #include <sqlite3.h>
49 #include "qgsprojutils.h"
50 #include <proj.h>
51 #include <proj_experimental.h>
52 
53 //gdal and ogr includes (needed for == operator)
54 #include <ogr_srs_api.h>
55 #include <cpl_error.h>
56 #include <cpl_conv.h>
57 #include <cpl_csv.h>
58 
59 CUSTOM_CRS_VALIDATION QgsCoordinateReferenceSystem::sCustomSrsValidation = nullptr;
60 
61 typedef QHash< long, QgsCoordinateReferenceSystem > SrIdCrsCacheHash;
62 typedef QHash< QString, QgsCoordinateReferenceSystem > StringCrsCacheHash;
63 
64 Q_GLOBAL_STATIC( QReadWriteLock, sSrIdCacheLock )
65 Q_GLOBAL_STATIC( SrIdCrsCacheHash, sSrIdCache )
66 bool QgsCoordinateReferenceSystem::sDisableSrIdCache = false;
67 
68 Q_GLOBAL_STATIC( QReadWriteLock, sOgcLock )
70 bool QgsCoordinateReferenceSystem::sDisableOgcCache = false;
71 
72 Q_GLOBAL_STATIC( QReadWriteLock, sProj4CacheLock )
73 Q_GLOBAL_STATIC( StringCrsCacheHash, sProj4Cache )
74 bool QgsCoordinateReferenceSystem::sDisableProjCache = false;
75 
76 Q_GLOBAL_STATIC( QReadWriteLock, sCRSWktLock )
78 bool QgsCoordinateReferenceSystem::sDisableWktCache = false;
79 
80 Q_GLOBAL_STATIC( QReadWriteLock, sCRSSrsIdLock )
81 Q_GLOBAL_STATIC( SrIdCrsCacheHash, sSrsIdCache )
82 bool QgsCoordinateReferenceSystem::sDisableSrsIdCache = false;
83 
84 Q_GLOBAL_STATIC( QReadWriteLock, sCrsStringLock )
85 Q_GLOBAL_STATIC( StringCrsCacheHash, sStringCache )
86 bool QgsCoordinateReferenceSystem::sDisableStringCache = false;
87 
88 QString getFullProjString( PJ *obj )
89 {
90  // see https://lists.osgeo.org/pipermail/proj/2019-May/008565.html, it's not sufficient to just
91  // use proj_as_proj_string
92  QgsProjUtils::proj_pj_unique_ptr boundCrs( proj_crs_create_bound_crs_to_WGS84( QgsProjContext::get(), obj, nullptr ) );
93  if ( boundCrs )
94  {
95  if ( const char *proj4src = proj_as_proj_string( QgsProjContext::get(), boundCrs.get(), PJ_PROJ_4, nullptr ) )
96  {
97  return QString( proj4src );
98  }
99  }
100 
101  return QString( proj_as_proj_string( QgsProjContext::get(), obj, PJ_PROJ_4, nullptr ) );
102 }
103 //--------------------------
104 
106 {
107  static QgsCoordinateReferenceSystem nullCrs = QgsCoordinateReferenceSystem( QString() );
108 
109  d = nullCrs.d;
110 }
111 
113 {
114  d = new QgsCoordinateReferenceSystemPrivate();
115  createFromString( definition );
116 }
117 
119 {
120  d = new QgsCoordinateReferenceSystemPrivate();
122  createFromId( id, type );
124 }
125 
127  : d( srs.d )
128  , mValidationHint( srs.mValidationHint )
129  , mNativeFormat( srs.mNativeFormat )
130 {
131 }
132 
134 {
135  d = srs.d;
136  mValidationHint = srs.mValidationHint;
137  mNativeFormat = srs.mNativeFormat;
138  return *this;
139 }
140 
142 {
143  QList<long> results;
144  // check both standard & user defined projection databases
145  QStringList dbs = QStringList() << QgsApplication::srsDatabaseFilePath() << QgsApplication::qgisUserDatabaseFilePath();
146 
147  const auto constDbs = dbs;
148  for ( const QString &db : constDbs )
149  {
150  QFileInfo myInfo( db );
151  if ( !myInfo.exists() )
152  {
153  QgsDebugError( "failed : " + db + " does not exist!" );
154  continue;
155  }
156 
159 
160  //check the db is available
161  int result = openDatabase( db, database );
162  if ( result != SQLITE_OK )
163  {
164  QgsDebugError( "failed : " + db + " could not be opened!" );
165  continue;
166  }
167 
168  QString sql = QStringLiteral( "select srs_id from tbl_srs" );
169  int rc;
170  statement = database.prepare( sql, rc );
171  while ( true )
172  {
173  // this one is an infinitive loop, intended to fetch any row
174  int ret = statement.step();
175 
176  if ( ret == SQLITE_DONE )
177  {
178  // there are no more rows to fetch - we can stop looping
179  break;
180  }
181 
182  if ( ret == SQLITE_ROW )
183  {
184  results.append( statement.columnAsInt64( 0 ) );
185  }
186  else
187  {
188  QgsMessageLog::logMessage( QObject::tr( "SQLite error: %2\nSQL: %1" ).arg( sql, sqlite3_errmsg( database.get() ) ), QObject::tr( "SpatiaLite" ) );
189  break;
190  }
191  }
192  }
193  std::sort( results.begin(), results.end() );
194  return results;
195 }
196 
198 {
200  crs.createFromOgcWmsCrs( ogcCrs );
201  return crs;
202 }
203 
205 {
206  QgsCoordinateReferenceSystem res = fromOgcWmsCrs( "EPSG:" + QString::number( epsg ) );
207  if ( res.isValid() )
208  return res;
209 
210  // pre proj6 builds allowed use of ESRI: codes here (e.g. 54030), so we need to keep compatibility
211  res = fromOgcWmsCrs( "ESRI:" + QString::number( epsg ) );
212  if ( res.isValid() )
213  return res;
214 
216 }
217 
219 {
220  return fromProj( proj4 );
221 }
222 
224 {
226  crs.createFromProj( proj );
227  return crs;
228 }
229 
231 {
233  crs.createFromWkt( wkt );
234  return crs;
235 }
236 
238 {
240  crs.createFromSrsId( srsId );
241  return crs;
242 }
243 
245 {
246  PJ *horizontalObj = horizontalCrs.projObject();
247  PJ *verticalObj = verticalCrs.projObject();
248  if ( horizontalObj && verticalObj )
249  {
250  QgsProjUtils::proj_pj_unique_ptr compoundCrs = QgsProjUtils::createCompoundCrs( horizontalObj, verticalObj );
251  if ( compoundCrs )
252  return QgsCoordinateReferenceSystem::fromProjObject( compoundCrs.get() );
253  }
255 }
256 
258 {
259 }
260 
262 {
263  bool result = false;
264  switch ( type )
265  {
266  case InternalCrsId:
267  result = createFromSrsId( id );
268  break;
269  case PostgisCrsId:
271  result = createFromSrid( id );
273  break;
274  case EpsgCrsId:
275  result = createFromOgcWmsCrs( QStringLiteral( "EPSG:%1" ).arg( id ) );
276  break;
277  default:
278  //THIS IS BAD...THIS PART OF CODE SHOULD NEVER BE REACHED...
279  QgsDebugError( QStringLiteral( "Unexpected case reached!" ) );
280  };
281  return result;
282 }
283 
284 bool QgsCoordinateReferenceSystem::createFromString( const QString &definition )
285 {
286  if ( definition.isEmpty() )
287  return false;
288 
289  QgsReadWriteLocker locker( *sCrsStringLock(), QgsReadWriteLocker::Read );
290  if ( !sDisableStringCache )
291  {
292  QHash< QString, QgsCoordinateReferenceSystem >::const_iterator crsIt = sStringCache()->constFind( definition );
293  if ( crsIt != sStringCache()->constEnd() )
294  {
295  // found a match in the cache
296  *this = crsIt.value();
297  return d->mIsValid;
298  }
299  }
300  locker.unlock();
301 
302  bool result = false;
303  const thread_local QRegularExpression reCrsId( QStringLiteral( "^(epsg|esri|osgeo|ignf|ogc|nkg|zangi|iau_2015|iau2000|postgis|internal|user)\\:(\\w+)$" ), QRegularExpression::CaseInsensitiveOption );
304  QRegularExpressionMatch match = reCrsId.match( definition );
305  if ( match.capturedStart() == 0 )
306  {
307  QString authName = match.captured( 1 ).toLower();
308  if ( authName == QLatin1String( "epsg" ) )
309  {
310  result = createFromOgcWmsCrs( definition );
311  }
312  else if ( authName == QLatin1String( "postgis" ) )
313  {
314  const long id = match.captured( 2 ).toLong();
316  result = createFromSrid( id );
318  }
319  else if ( authName == QLatin1String( "esri" )
320  || authName == QLatin1String( "osgeo" )
321  || authName == QLatin1String( "ignf" )
322  || authName == QLatin1String( "zangi" )
323  || authName == QLatin1String( "iau2000" )
324  || authName == QLatin1String( "ogc" )
325  || authName == QLatin1String( "nkg" )
326  || authName == QLatin1String( "iau_2015" )
327  )
328  {
329  result = createFromOgcWmsCrs( definition );
330  }
331  else
332  {
333  const long id = match.captured( 2 ).toLong();
335  result = createFromId( id, InternalCrsId );
337  }
338  }
339  else
340  {
341  const thread_local QRegularExpression reCrsStr( QStringLiteral( "^(?:(wkt|proj4|proj)\\:)?(.+)$" ), QRegularExpression::CaseInsensitiveOption );
342  match = reCrsStr.match( definition );
343  if ( match.capturedStart() == 0 )
344  {
345  if ( match.captured( 1 ).startsWith( QLatin1String( "proj" ), Qt::CaseInsensitive ) )
346  {
347  result = createFromProj( match.captured( 2 ) );
348  }
349  else
350  {
351  result = createFromWkt( match.captured( 2 ) );
352  }
353  }
354  }
355 
357  if ( !sDisableStringCache )
358  sStringCache()->insert( definition, *this );
359  return result;
360 }
361 
362 bool QgsCoordinateReferenceSystem::createFromUserInput( const QString &definition )
363 {
364  if ( definition.isEmpty() )
365  return false;
366 
367  QString userWkt;
368  OGRSpatialReferenceH crs = OSRNewSpatialReference( nullptr );
369 
370  if ( OSRSetFromUserInput( crs, definition.toLocal8Bit().constData() ) == OGRERR_NONE )
371  {
373  OSRDestroySpatialReference( crs );
374  }
375  //QgsDebugMsgLevel( "definition: " + definition + " wkt = " + wkt, 2 );
376  return createFromWkt( userWkt );
377 }
378 
380 {
381  // make sure towgs84 parameter is loaded if gdal >= 1.9
382  // this requires setting GDAL_FIX_ESRI_WKT=GEOGCS (see qgis bug #5598 and gdal bug #4673)
383  const char *configOld = CPLGetConfigOption( "GDAL_FIX_ESRI_WKT", "" );
384  const char *configNew = "GEOGCS";
385  // only set if it was not set, to let user change the value if needed
386  if ( strcmp( configOld, "" ) == 0 )
387  {
388  CPLSetConfigOption( "GDAL_FIX_ESRI_WKT", configNew );
389  if ( strcmp( configNew, CPLGetConfigOption( "GDAL_FIX_ESRI_WKT", "" ) ) != 0 )
390  QgsLogger::warning( QStringLiteral( "GDAL_FIX_ESRI_WKT could not be set to %1 : %2" )
391  .arg( configNew, CPLGetConfigOption( "GDAL_FIX_ESRI_WKT", "" ) ) );
392  QgsDebugMsgLevel( QStringLiteral( "set GDAL_FIX_ESRI_WKT : %1" ).arg( configNew ), 4 );
393  }
394  else
395  {
396  QgsDebugMsgLevel( QStringLiteral( "GDAL_FIX_ESRI_WKT was already set : %1" ).arg( configNew ), 4 );
397  }
398 }
399 
401 {
402  if ( crs.isEmpty() )
403  return false;
404 
405  QgsReadWriteLocker locker( *sOgcLock(), QgsReadWriteLocker::Read );
406  if ( !sDisableOgcCache )
407  {
408  QHash< QString, QgsCoordinateReferenceSystem >::const_iterator crsIt = sOgcCache()->constFind( crs );
409  if ( crsIt != sOgcCache()->constEnd() )
410  {
411  // found a match in the cache
412  *this = crsIt.value();
413  return d->mIsValid;
414  }
415  }
416  locker.unlock();
417 
418  QString wmsCrs = crs;
419 
420  QString authority;
421  QString code;
422  const QgsOgcCrsUtils::CRSFlavor crsFlavor = QgsOgcCrsUtils::parseCrsName( crs, authority, code );
423  const QString authorityLower = authority.toLower();
424  if ( crsFlavor == QgsOgcCrsUtils::CRSFlavor::AUTH_CODE &&
425  ( authorityLower == QLatin1String( "user" ) ||
426  authorityLower == QLatin1String( "custom" ) ||
427  authorityLower == QLatin1String( "qgis" ) ) )
428  {
429  if ( createFromSrsId( code.toInt() ) )
430  {
432  if ( !sDisableOgcCache )
433  sOgcCache()->insert( crs, *this );
434  return d->mIsValid;
435  }
436  }
437  else if ( crsFlavor != QgsOgcCrsUtils::CRSFlavor::UNKNOWN )
438  {
439  wmsCrs = authority + ':' + code;
440  }
441 
442  // first chance for proj 6 - scan through legacy systems and try to use authid directly
443  const QString legacyKey = wmsCrs.toLower();
444  for ( auto it = sAuthIdToQgisSrsIdMap.constBegin(); it != sAuthIdToQgisSrsIdMap.constEnd(); ++it )
445  {
446  if ( it.key().compare( legacyKey, Qt::CaseInsensitive ) == 0 )
447  {
448  const QStringList parts = it.key().split( ':' );
449  const QString auth = parts.at( 0 );
450  const QString code = parts.at( 1 );
451  if ( loadFromAuthCode( auth, code ) )
452  {
454  if ( !sDisableOgcCache )
455  sOgcCache()->insert( crs, *this );
456  return d->mIsValid;
457  }
458  }
459  }
460 
461  if ( loadFromDatabase( QgsApplication::srsDatabaseFilePath(), QStringLiteral( "lower(auth_name||':'||auth_id)" ), wmsCrs.toLower() ) )
462  {
464  if ( !sDisableOgcCache )
465  sOgcCache()->insert( crs, *this );
466  return d->mIsValid;
467  }
468 
469  // NAD27
470  if ( wmsCrs.compare( QLatin1String( "CRS:27" ), Qt::CaseInsensitive ) == 0 ||
471  wmsCrs.compare( QLatin1String( "OGC:CRS27" ), Qt::CaseInsensitive ) == 0 )
472  {
473  // TODO: verify same axis orientation
474  return createFromOgcWmsCrs( QStringLiteral( "EPSG:4267" ) );
475  }
476 
477  // NAD83
478  if ( wmsCrs.compare( QLatin1String( "CRS:83" ), Qt::CaseInsensitive ) == 0 ||
479  wmsCrs.compare( QLatin1String( "OGC:CRS83" ), Qt::CaseInsensitive ) == 0 )
480  {
481  // TODO: verify same axis orientation
482  return createFromOgcWmsCrs( QStringLiteral( "EPSG:4269" ) );
483  }
484 
485  // WGS84
486  if ( wmsCrs.compare( QLatin1String( "CRS:84" ), Qt::CaseInsensitive ) == 0 ||
487  wmsCrs.compare( QLatin1String( "OGC:CRS84" ), Qt::CaseInsensitive ) == 0 )
488  {
489  if ( loadFromDatabase( QgsApplication::srsDatabaseFilePath(), QStringLiteral( "lower(auth_name||':'||auth_id)" ), QStringLiteral( "epsg:4326" ) ) )
490  {
491  d->mAxisInverted = false;
492  d->mAxisInvertedDirty = false;
493  }
494 
496  if ( !sDisableOgcCache )
497  sOgcCache()->insert( crs, *this );
498 
499  return d->mIsValid;
500  }
501 
502  // Try loading from Proj's db using authority and code
503  // While this CRS wasn't found in QGIS' srs db, it may be present in proj's
504  if ( !authority.isEmpty() && !code.isEmpty() && loadFromAuthCode( authority, code ) )
505  {
507  if ( !sDisableOgcCache )
508  sOgcCache()->insert( crs, *this );
509  return d->mIsValid;
510  }
511 
513  if ( !sDisableOgcCache )
514  sOgcCache()->insert( crs, QgsCoordinateReferenceSystem() );
515  return d->mIsValid;
516 }
517 
518 // Misc helper functions -----------------------
519 
520 
522 {
523  if ( d->mIsValid || !sCustomSrsValidation )
524  return;
525 
526  // try to validate using custom validation routines
527  if ( sCustomSrsValidation )
528  sCustomSrsValidation( *this );
529 }
530 
532 {
533  QgsReadWriteLocker locker( *sSrIdCacheLock(), QgsReadWriteLocker::Read );
534  if ( !sDisableSrIdCache )
535  {
536  QHash< long, QgsCoordinateReferenceSystem >::const_iterator crsIt = sSrIdCache()->constFind( id );
537  if ( crsIt != sSrIdCache()->constEnd() )
538  {
539  // found a match in the cache
540  *this = crsIt.value();
541  return d->mIsValid;
542  }
543  }
544  locker.unlock();
545 
546  // first chance for proj 6 - scan through legacy systems and try to use authid directly
547  for ( auto it = sAuthIdToQgisSrsIdMap.constBegin(); it != sAuthIdToQgisSrsIdMap.constEnd(); ++it )
548  {
549  if ( it.value().endsWith( QStringLiteral( ",%1" ).arg( id ) ) )
550  {
551  const QStringList parts = it.key().split( ':' );
552  const QString auth = parts.at( 0 );
553  const QString code = parts.at( 1 );
554  if ( loadFromAuthCode( auth, code ) )
555  {
557  if ( !sDisableSrIdCache )
558  sSrIdCache()->insert( id, *this );
559 
560  return d->mIsValid;
561  }
562  }
563  }
564 
565  bool result = loadFromDatabase( QgsApplication::srsDatabaseFilePath(), QStringLiteral( "srid" ), QString::number( id ) );
566 
568  if ( !sDisableSrIdCache )
569  sSrIdCache()->insert( id, *this );
570 
571  return result;
572 }
573 
575 {
576  QgsReadWriteLocker locker( *sCRSSrsIdLock(), QgsReadWriteLocker::Read );
577  if ( !sDisableSrsIdCache )
578  {
579  QHash< long, QgsCoordinateReferenceSystem >::const_iterator crsIt = sSrsIdCache()->constFind( id );
580  if ( crsIt != sSrsIdCache()->constEnd() )
581  {
582  // found a match in the cache
583  *this = crsIt.value();
584  return d->mIsValid;
585  }
586  }
587  locker.unlock();
588 
589  // first chance for proj 6 - scan through legacy systems and try to use authid directly
590  for ( auto it = sAuthIdToQgisSrsIdMap.constBegin(); it != sAuthIdToQgisSrsIdMap.constEnd(); ++it )
591  {
592  if ( it.value().startsWith( QString::number( id ) + ',' ) )
593  {
594  const QStringList parts = it.key().split( ':' );
595  const QString auth = parts.at( 0 );
596  const QString code = parts.at( 1 );
597  if ( loadFromAuthCode( auth, code ) )
598  {
600  if ( !sDisableSrsIdCache )
601  sSrsIdCache()->insert( id, *this );
602  return d->mIsValid;
603  }
604  }
605  }
606 
607  bool result = loadFromDatabase( id < USER_CRS_START_ID ? QgsApplication::srsDatabaseFilePath() :
609  QStringLiteral( "srs_id" ), QString::number( id ) );
610 
612  if ( !sDisableSrsIdCache )
613  sSrsIdCache()->insert( id, *this );
614  return result;
615 }
616 
617 bool QgsCoordinateReferenceSystem::loadFromDatabase( const QString &db, const QString &expression, const QString &value )
618 {
619  d.detach();
620 
621  QgsDebugMsgLevel( "load CRS from " + db + " where " + expression + " is " + value, 3 );
622  d->mIsValid = false;
623  d->mWktPreferred.clear();
624 
625  QFileInfo myInfo( db );
626  if ( !myInfo.exists() )
627  {
628  QgsDebugError( "failed : " + db + " does not exist!" );
629  return d->mIsValid;
630  }
631 
634  int myResult;
635  //check the db is available
636  myResult = openDatabase( db, database );
637  if ( myResult != SQLITE_OK )
638  {
639  return d->mIsValid;
640  }
641 
642  /*
643  srs_id INTEGER PRIMARY KEY,
644  description text NOT NULL,
645  projection_acronym text NOT NULL,
646  ellipsoid_acronym NOT NULL,
647  parameters text NOT NULL,
648  srid integer NOT NULL,
649  auth_name varchar NOT NULL,
650  auth_id integer NOT NULL,
651  is_geo integer NOT NULL);
652  */
653 
654  QString mySql = "select srs_id,description,projection_acronym,"
655  "ellipsoid_acronym,parameters,srid,auth_name||':'||auth_id,is_geo,wkt "
656  "from tbl_srs where " + expression + '=' + QgsSqliteUtils::quotedString( value ) + " order by deprecated";
657  statement = database.prepare( mySql, myResult );
658  QString wkt;
659  // XXX Need to free memory from the error msg if one is set
660  if ( myResult == SQLITE_OK && statement.step() == SQLITE_ROW )
661  {
662  d->mSrsId = statement.columnAsText( 0 ).toLong();
663  d->mDescription = statement.columnAsText( 1 );
664  d->mProjectionAcronym = statement.columnAsText( 2 );
665  d->mEllipsoidAcronym.clear();
666  d->mProj4 = statement.columnAsText( 4 );
667  d->mWktPreferred.clear();
668  d->mSRID = statement.columnAsText( 5 ).toLong();
669  d->mAuthId = statement.columnAsText( 6 );
670  d->mIsGeographic = statement.columnAsText( 7 ).toInt() != 0;
671  wkt = statement.columnAsText( 8 );
672  d->mAxisInvertedDirty = true;
673 
674  if ( d->mSrsId >= USER_CRS_START_ID && ( d->mAuthId.isEmpty() || d->mAuthId == QChar( ':' ) ) )
675  {
676  d->mAuthId = QStringLiteral( "USER:%1" ).arg( d->mSrsId );
677  }
678  else if ( !d->mAuthId.startsWith( QLatin1String( "USER:" ), Qt::CaseInsensitive ) )
679  {
680  QStringList parts = d->mAuthId.split( ':' );
681  QString auth = parts.at( 0 );
682  QString code = parts.at( 1 );
683 
684  {
685  QgsProjUtils::proj_pj_unique_ptr crs( proj_create_from_database( QgsProjContext::get(), auth.toLatin1(), code.toLatin1(), PJ_CATEGORY_CRS, false, nullptr ) );
686  d->setPj( QgsProjUtils::unboundCrs( crs.get() ) );
687  }
688 
689  d->mIsValid = d->hasPj();
690  setMapUnits();
691  }
692 
693  if ( !d->mIsValid )
694  {
695  if ( !wkt.isEmpty() )
696  {
697  setWktString( wkt );
698  // set WKT string resets the description to that description embedded in the WKT, so manually overwrite this back to the
699  // value from the user DB
700  d->mDescription = statement.columnAsText( 1 );
701  }
702  else
703  setProjString( d->mProj4 );
704  }
705  }
706  else
707  {
708  QgsDebugMsgLevel( "failed : " + mySql, 4 );
709  }
710  return d->mIsValid;
711 }
712 
713 void QgsCoordinateReferenceSystem::removeFromCacheObjectsBelongingToCurrentThread( PJ_CONTEXT *pj_context )
714 {
715  // Not completely sure about object order destruction after main() has
716  // exited. So it is safer to check sDisableCache before using sCacheLock
717  // in case sCacheLock would have been destroyed before the current TLS
718  // QgsProjContext object that has called us...
719 
720  if ( !sDisableSrIdCache )
721  {
722  QgsReadWriteLocker locker( *sSrIdCacheLock(), QgsReadWriteLocker::Write );
723  if ( !sDisableSrIdCache )
724  {
725  for ( auto it = sSrIdCache()->begin(); it != sSrIdCache()->end(); )
726  {
727  auto &v = it.value();
728  if ( v.d->removeObjectsBelongingToCurrentThread( pj_context ) )
729  it = sSrIdCache()->erase( it );
730  else
731  ++it;
732  }
733  }
734  }
735  if ( !sDisableOgcCache )
736  {
737  QgsReadWriteLocker locker( *sOgcLock(), QgsReadWriteLocker::Write );
738  if ( !sDisableOgcCache )
739  {
740  for ( auto it = sOgcCache()->begin(); it != sOgcCache()->end(); )
741  {
742  auto &v = it.value();
743  if ( v.d->removeObjectsBelongingToCurrentThread( pj_context ) )
744  it = sOgcCache()->erase( it );
745  else
746  ++it;
747  }
748  }
749  }
750  if ( !sDisableProjCache )
751  {
752  QgsReadWriteLocker locker( *sProj4CacheLock(), QgsReadWriteLocker::Write );
753  if ( !sDisableProjCache )
754  {
755  for ( auto it = sProj4Cache()->begin(); it != sProj4Cache()->end(); )
756  {
757  auto &v = it.value();
758  if ( v.d->removeObjectsBelongingToCurrentThread( pj_context ) )
759  it = sProj4Cache()->erase( it );
760  else
761  ++it;
762  }
763  }
764  }
765  if ( !sDisableWktCache )
766  {
767  QgsReadWriteLocker locker( *sCRSWktLock(), QgsReadWriteLocker::Write );
768  if ( !sDisableWktCache )
769  {
770  for ( auto it = sWktCache()->begin(); it != sWktCache()->end(); )
771  {
772  auto &v = it.value();
773  if ( v.d->removeObjectsBelongingToCurrentThread( pj_context ) )
774  it = sWktCache()->erase( it );
775  else
776  ++it;
777  }
778  }
779  }
780  if ( !sDisableSrsIdCache )
781  {
782  QgsReadWriteLocker locker( *sCRSSrsIdLock(), QgsReadWriteLocker::Write );
783  if ( !sDisableSrsIdCache )
784  {
785  for ( auto it = sSrsIdCache()->begin(); it != sSrsIdCache()->end(); )
786  {
787  auto &v = it.value();
788  if ( v.d->removeObjectsBelongingToCurrentThread( pj_context ) )
789  it = sSrsIdCache()->erase( it );
790  else
791  ++it;
792  }
793  }
794  }
795  if ( !sDisableStringCache )
796  {
797  QgsReadWriteLocker locker( *sCrsStringLock(), QgsReadWriteLocker::Write );
798  if ( !sDisableStringCache )
799  {
800  for ( auto it = sStringCache()->begin(); it != sStringCache()->end(); )
801  {
802  auto &v = it.value();
803  if ( v.d->removeObjectsBelongingToCurrentThread( pj_context ) )
804  it = sStringCache()->erase( it );
805  else
806  ++it;
807  }
808  }
809  }
810 }
811 
813 {
814  if ( d->mAxisInvertedDirty )
815  {
816  d->mAxisInverted = QgsProjUtils::axisOrderIsSwapped( d->threadLocalProjObject() );
817  d->mAxisInvertedDirty = false;
818  }
819 
820  return d->mAxisInverted;
821 }
822 
823 QList<Qgis::CrsAxisDirection> QgsCoordinateReferenceSystem::axisOrdering() const
824 {
825  const PJ *projObject = d->threadLocalProjObject();
826  if ( !projObject )
827  return {};
828 
829  PJ_CONTEXT *context = QgsProjContext::get();
830  QgsProjUtils::proj_pj_unique_ptr pjCs( proj_crs_get_coordinate_system( context, projObject ) );
831  if ( !pjCs )
832  return {};
833 
834  const thread_local QMap< Qgis::CrsAxisDirection, QString > mapping =
835  {
836  { Qgis::CrsAxisDirection::North, QStringLiteral( "north" ) },
837  { Qgis::CrsAxisDirection::NorthNorthEast, QStringLiteral( "northNorthEast" ) },
838  { Qgis::CrsAxisDirection::NorthEast, QStringLiteral( "northEast" ) },
839  { Qgis::CrsAxisDirection::EastNorthEast, QStringLiteral( "eastNorthEast" ) },
840  { Qgis::CrsAxisDirection::East, QStringLiteral( "east" ) },
841  { Qgis::CrsAxisDirection::EastSouthEast, QStringLiteral( "eastSouthEast" ) },
842  { Qgis::CrsAxisDirection::SouthEast, QStringLiteral( "southEast" ) },
843  { Qgis::CrsAxisDirection::SouthSouthEast, QStringLiteral( "southSouthEast" ) },
844  { Qgis::CrsAxisDirection::South, QStringLiteral( "south" ) },
845  { Qgis::CrsAxisDirection::SouthSouthWest, QStringLiteral( "southSouthWest" ) },
846  { Qgis::CrsAxisDirection::SouthWest, QStringLiteral( "southWest" ) },
847  { Qgis::CrsAxisDirection::WestSouthWest, QStringLiteral( "westSouthWest" ) },
848  { Qgis::CrsAxisDirection::West, QStringLiteral( "west" ) },
849  { Qgis::CrsAxisDirection::WestNorthWest, QStringLiteral( "westNorthWest" ) },
850  { Qgis::CrsAxisDirection::NorthWest, QStringLiteral( "northWest" ) },
851  { Qgis::CrsAxisDirection::NorthNorthWest, QStringLiteral( "northNorthWest" ) },
852  { Qgis::CrsAxisDirection::GeocentricX, QStringLiteral( "geocentricX" ) },
853  { Qgis::CrsAxisDirection::GeocentricY, QStringLiteral( "geocentricY" ) },
854  { Qgis::CrsAxisDirection::GeocentricZ, QStringLiteral( "geocentricZ" ) },
855  { Qgis::CrsAxisDirection::Up, QStringLiteral( "up" ) },
856  { Qgis::CrsAxisDirection::Down, QStringLiteral( "down" ) },
857  { Qgis::CrsAxisDirection::Forward, QStringLiteral( "forward" ) },
858  { Qgis::CrsAxisDirection::Aft, QStringLiteral( "aft" ) },
859  { Qgis::CrsAxisDirection::Port, QStringLiteral( "port" ) },
860  { Qgis::CrsAxisDirection::Starboard, QStringLiteral( "starboard" ) },
861  { Qgis::CrsAxisDirection::Clockwise, QStringLiteral( "clockwise" ) },
862  { Qgis::CrsAxisDirection::CounterClockwise, QStringLiteral( "counterClockwise" ) },
863  { Qgis::CrsAxisDirection::ColumnPositive, QStringLiteral( "columnPositive" ) },
864  { Qgis::CrsAxisDirection::ColumnNegative, QStringLiteral( "columnNegative" ) },
865  { Qgis::CrsAxisDirection::RowPositive, QStringLiteral( "rowPositive" ) },
866  { Qgis::CrsAxisDirection::RowNegative, QStringLiteral( "rowNegative" ) },
867  { Qgis::CrsAxisDirection::DisplayRight, QStringLiteral( "displayRight" ) },
868  { Qgis::CrsAxisDirection::DisplayLeft, QStringLiteral( "displayLeft" ) },
869  { Qgis::CrsAxisDirection::DisplayUp, QStringLiteral( "displayUp" ) },
870  { Qgis::CrsAxisDirection::DisplayDown, QStringLiteral( "displayDown" ) },
871  { Qgis::CrsAxisDirection::Future, QStringLiteral( "future" ) },
872  { Qgis::CrsAxisDirection::Past, QStringLiteral( "past" ) },
873  { Qgis::CrsAxisDirection::Towards, QStringLiteral( "towards" ) },
874  { Qgis::CrsAxisDirection::AwayFrom, QStringLiteral( "awayFrom" ) },
875  };
876 
877  QList< Qgis::CrsAxisDirection > res;
878  const int axisCount = proj_cs_get_axis_count( context, pjCs.get() );
879  if ( axisCount > 0 )
880  {
881  res.reserve( axisCount );
882 
883  for ( int i = 0; i < axisCount; ++i )
884  {
885  const char *outDirection = nullptr;
886  proj_cs_get_axis_info( context, pjCs.get(), i,
887  nullptr,
888  nullptr,
889  &outDirection,
890  nullptr,
891  nullptr,
892  nullptr,
893  nullptr
894  );
895  // get first word of direction only
896  const thread_local QRegularExpression rx( QStringLiteral( "([^\\s]+).*" ) );
897  const QRegularExpressionMatch match = rx.match( QString( outDirection ) );
898  if ( !match.hasMatch() )
899  continue;
900 
901  const QString direction = match.captured( 1 );
903  for ( auto it = mapping.constBegin(); it != mapping.constEnd(); ++it )
904  {
905  if ( it.value().compare( direction, Qt::CaseInsensitive ) == 0 )
906  {
907  dir = it.key();
908  break;
909  }
910  }
911 
912  res.append( dir );
913  }
914  }
915  return res;
916 }
917 
919 {
920  return createFromWktInternal( wkt, QString() );
921 }
922 
923 bool QgsCoordinateReferenceSystem::createFromWktInternal( const QString &wkt, const QString &description )
924 {
925  if ( wkt.isEmpty() )
926  return false;
927 
928  d.detach();
929 
930  QgsReadWriteLocker locker( *sCRSWktLock(), QgsReadWriteLocker::Read );
931  if ( !sDisableWktCache )
932  {
933  QHash< QString, QgsCoordinateReferenceSystem >::const_iterator crsIt = sWktCache()->constFind( wkt );
934  if ( crsIt != sWktCache()->constEnd() )
935  {
936  // found a match in the cache
937  *this = crsIt.value();
938 
939  if ( !description.isEmpty() && d->mDescription.isEmpty() )
940  {
941  // now we have a name for a previously unknown CRS! Update the cached CRS accordingly, so that we use the name from now on...
942  d->mDescription = description;
943  locker.changeMode( QgsReadWriteLocker::Write );
944  sWktCache()->insert( wkt, *this );
945  }
946  return d->mIsValid;
947  }
948  }
949  locker.unlock();
950 
951  d->mIsValid = false;
952  d->mProj4.clear();
953  d->mWktPreferred.clear();
954  if ( wkt.isEmpty() )
955  {
956  QgsDebugMsgLevel( QStringLiteral( "theWkt is uninitialized, operation failed" ), 4 );
957  return d->mIsValid;
958  }
959 
960  // try to match against user crs
961  QgsCoordinateReferenceSystem::RecordMap record = getRecord( "select * from tbl_srs where wkt=" + QgsSqliteUtils::quotedString( wkt ) + " order by deprecated" );
962  if ( !record.empty() )
963  {
964  long srsId = record[QStringLiteral( "srs_id" )].toLong();
965  if ( srsId > 0 )
966  {
967  createFromSrsId( srsId );
968  }
969  }
970  else
971  {
972  setWktString( wkt );
973  if ( !description.isEmpty() )
974  {
975  d->mDescription = description;
976  }
977  if ( d->mSrsId == 0 )
978  {
979  // lastly, try a tolerant match of the created proj object against all user CRSes (allowing differences in parameter order during the comparison)
980  long id = matchToUserCrs();
981  if ( id >= USER_CRS_START_ID )
982  {
983  createFromSrsId( id );
984  }
985  }
986  }
987 
988  locker.changeMode( QgsReadWriteLocker::Write );
989  if ( !sDisableWktCache )
990  sWktCache()->insert( wkt, *this );
991 
992  return d->mIsValid;
993  //setMapunits will be called by createfromproj above
994 }
995 
997 {
998  return d->mIsValid;
999 }
1000 
1001 bool QgsCoordinateReferenceSystem::createFromProj4( const QString &proj4String )
1002 {
1003  return createFromProj( proj4String );
1004 }
1005 
1006 bool QgsCoordinateReferenceSystem::createFromProj( const QString &projString, const bool identify )
1007 {
1008  if ( projString.isEmpty() )
1009  return false;
1010 
1011  d.detach();
1012 
1013  if ( projString.trimmed().isEmpty() )
1014  {
1015  d->mIsValid = false;
1016  d->mProj4.clear();
1017  d->mWktPreferred.clear();
1018  return false;
1019  }
1020 
1021  QgsReadWriteLocker locker( *sProj4CacheLock(), QgsReadWriteLocker::Read );
1022  if ( !sDisableProjCache )
1023  {
1024  QHash< QString, QgsCoordinateReferenceSystem >::const_iterator crsIt = sProj4Cache()->constFind( projString );
1025  if ( crsIt != sProj4Cache()->constEnd() )
1026  {
1027  // found a match in the cache
1028  *this = crsIt.value();
1029  return d->mIsValid;
1030  }
1031  }
1032  locker.unlock();
1033 
1034  //
1035  // Examples:
1036  // +proj=tmerc +lat_0=0 +lon_0=-62 +k=0.999500 +x_0=400000 +y_0=0
1037  // +ellps=clrk80 +towgs84=-255,-15,71,0,0,0,0 +units=m +no_defs
1038  //
1039  // +proj=lcc +lat_1=46.8 +lat_0=46.8 +lon_0=2.337229166666664 +k_0=0.99987742
1040  // +x_0=600000 +y_0=2200000 +a=6378249.2 +b=6356515.000000472 +units=m +no_defs
1041  //
1042  QString myProj4String = projString.trimmed();
1043  myProj4String.remove( QStringLiteral( "+type=crs" ) );
1044  myProj4String = myProj4String.trimmed();
1045 
1046  d->mIsValid = false;
1047  d->mWktPreferred.clear();
1048 
1049  if ( identify )
1050  {
1051  // first, try to use proj to do this for us...
1052  const QString projCrsString = myProj4String + ( myProj4String.contains( QStringLiteral( "+type=crs" ) ) ? QString() : QStringLiteral( " +type=crs" ) );
1053  QgsProjUtils::proj_pj_unique_ptr crs( proj_create( QgsProjContext::get(), projCrsString.toLatin1().constData() ) );
1054  if ( crs )
1055  {
1056  QString authName;
1057  QString authCode;
1059  {
1060  const QString authid = QStringLiteral( "%1:%2" ).arg( authName, authCode );
1061  if ( createFromOgcWmsCrs( authid ) )
1062  {
1064  if ( !sDisableProjCache )
1065  sProj4Cache()->insert( projString, *this );
1066  return d->mIsValid;
1067  }
1068  }
1069  }
1070 
1071  // try a direct match against user crses
1072  QgsCoordinateReferenceSystem::RecordMap myRecord = getRecord( "select * from tbl_srs where parameters=" + QgsSqliteUtils::quotedString( myProj4String ) + " order by deprecated" );
1073  long id = 0;
1074  if ( !myRecord.empty() )
1075  {
1076  id = myRecord[QStringLiteral( "srs_id" )].toLong();
1077  if ( id >= USER_CRS_START_ID )
1078  {
1079  createFromSrsId( id );
1080  }
1081  }
1082  if ( id < USER_CRS_START_ID )
1083  {
1084  // no direct matches, so go ahead and create a new proj object based on the proj string alone.
1085  setProjString( myProj4String );
1086 
1087  // lastly, try a tolerant match of the created proj object against all user CRSes (allowing differences in parameter order during the comparison)
1088  id = matchToUserCrs();
1089  if ( id >= USER_CRS_START_ID )
1090  {
1091  createFromSrsId( id );
1092  }
1093  }
1094  }
1095  else
1096  {
1097  setProjString( myProj4String );
1098  }
1099 
1101  if ( !sDisableProjCache )
1102  sProj4Cache()->insert( projString, *this );
1103 
1104  return d->mIsValid;
1105 }
1106 
1107 //private method meant for internal use by this class only
1108 QgsCoordinateReferenceSystem::RecordMap QgsCoordinateReferenceSystem::getRecord( const QString &sql )
1109 {
1110  QString myDatabaseFileName;
1111  QgsCoordinateReferenceSystem::RecordMap myMap;
1112  QString myFieldName;
1113  QString myFieldValue;
1114  sqlite3_database_unique_ptr database;
1115  sqlite3_statement_unique_ptr statement;
1116  int myResult;
1117 
1118  // Get the full path name to the sqlite3 spatial reference database.
1119  myDatabaseFileName = QgsApplication::srsDatabaseFilePath();
1120  QFileInfo myInfo( myDatabaseFileName );
1121  if ( !myInfo.exists() )
1122  {
1123  QgsDebugError( "failed : " + myDatabaseFileName + " does not exist!" );
1124  return myMap;
1125  }
1126 
1127  //check the db is available
1128  myResult = openDatabase( myDatabaseFileName, database );
1129  if ( myResult != SQLITE_OK )
1130  {
1131  return myMap;
1132  }
1133 
1134  statement = database.prepare( sql, myResult );
1135  // XXX Need to free memory from the error msg if one is set
1136  if ( myResult == SQLITE_OK && statement.step() == SQLITE_ROW )
1137  {
1138  int myColumnCount = statement.columnCount();
1139  //loop through each column in the record adding its expression name and value to the map
1140  for ( int myColNo = 0; myColNo < myColumnCount; myColNo++ )
1141  {
1142  myFieldName = statement.columnName( myColNo );
1143  myFieldValue = statement.columnAsText( myColNo );
1144  myMap[myFieldName] = myFieldValue;
1145  }
1146  if ( statement.step() != SQLITE_DONE )
1147  {
1148  QgsDebugMsgLevel( QStringLiteral( "Multiple records found in srs.db" ), 4 );
1149  //be less fussy on proj 6 -- the db has MANY more entries!
1150  }
1151  }
1152  else
1153  {
1154  QgsDebugMsgLevel( "failed : " + sql, 4 );
1155  }
1156 
1157  if ( myMap.empty() )
1158  {
1159  myDatabaseFileName = QgsApplication::qgisUserDatabaseFilePath();
1160  QFileInfo myFileInfo;
1161  myFileInfo.setFile( myDatabaseFileName );
1162  if ( !myFileInfo.exists() )
1163  {
1164  QgsDebugError( QStringLiteral( "user qgis.db not found" ) );
1165  return myMap;
1166  }
1167 
1168  //check the db is available
1169  myResult = openDatabase( myDatabaseFileName, database );
1170  if ( myResult != SQLITE_OK )
1171  {
1172  return myMap;
1173  }
1174 
1175  statement = database.prepare( sql, myResult );
1176  // XXX Need to free memory from the error msg if one is set
1177  if ( myResult == SQLITE_OK && statement.step() == SQLITE_ROW )
1178  {
1179  int myColumnCount = statement.columnCount();
1180  //loop through each column in the record adding its field name and value to the map
1181  for ( int myColNo = 0; myColNo < myColumnCount; myColNo++ )
1182  {
1183  myFieldName = statement.columnName( myColNo );
1184  myFieldValue = statement.columnAsText( myColNo );
1185  myMap[myFieldName] = myFieldValue;
1186  }
1187 
1188  if ( statement.step() != SQLITE_DONE )
1189  {
1190  QgsDebugMsgLevel( QStringLiteral( "Multiple records found in srs.db" ), 4 );
1191  myMap.clear();
1192  }
1193  }
1194  else
1195  {
1196  QgsDebugMsgLevel( "failed : " + sql, 4 );
1197  }
1198  }
1199  return myMap;
1200 }
1201 
1202 // Accessors -----------------------------------
1203 
1205 {
1206  return d->mSrsId;
1207 }
1208 
1210 {
1211  return d->mSRID;
1212 }
1213 
1215 {
1216  return d->mAuthId;
1217 }
1218 
1220 {
1221  if ( d->mDescription.isNull() )
1222  {
1223  return QString();
1224  }
1225  else
1226  {
1227  return d->mDescription;
1228  }
1229 }
1230 
1232 {
1233  QString id;
1234  if ( !authid().isEmpty() )
1235  {
1236  if ( type != Qgis::CrsIdentifierType::ShortString && !description().isEmpty() )
1237  id = QStringLiteral( "%1 - %2" ).arg( authid(), description() );
1238  else
1239  id = authid();
1240  }
1241  else if ( !description().isEmpty() )
1242  id = description();
1244  id = isValid() ? QObject::tr( "Custom CRS" ) : QObject::tr( "Unknown CRS" );
1245  else if ( !toWkt( Qgis::CrsWktVariant::Preferred ).isEmpty() )
1246  id = QObject::tr( "Custom CRS: %1" ).arg(
1247  type == Qgis::CrsIdentifierType::MediumString ? ( toWkt( Qgis::CrsWktVariant::Preferred ).left( 50 ) + QString( QChar( 0x2026 ) ) )
1249  else if ( !toProj().isEmpty() )
1250  id = QObject::tr( "Custom CRS: %1" ).arg( type == Qgis::CrsIdentifierType::MediumString ? ( toProj().left( 50 ) + QString( QChar( 0x2026 ) ) )
1251  : toProj() );
1252  if ( !id.isEmpty() && !std::isnan( d->mCoordinateEpoch ) )
1253  id += QStringLiteral( " @ %1" ).arg( d->mCoordinateEpoch );
1254 
1255  return id;
1256 }
1257 
1259 {
1260  if ( d->mProjectionAcronym.isNull() )
1261  {
1262  return QString();
1263  }
1264  else
1265  {
1266  return d->mProjectionAcronym;
1267  }
1268 }
1269 
1271 {
1272  if ( d->mEllipsoidAcronym.isNull() )
1273  {
1274  if ( PJ *obj = d->threadLocalProjObject() )
1275  {
1276  QgsProjUtils::proj_pj_unique_ptr ellipsoid( proj_get_ellipsoid( QgsProjContext::get(), obj ) );
1277  if ( ellipsoid )
1278  {
1279  const QString ellipsoidAuthName( proj_get_id_auth_name( ellipsoid.get(), 0 ) );
1280  const QString ellipsoidAuthCode( proj_get_id_code( ellipsoid.get(), 0 ) );
1281  if ( !ellipsoidAuthName.isEmpty() && !ellipsoidAuthCode.isEmpty() )
1282  d->mEllipsoidAcronym = QStringLiteral( "%1:%2" ).arg( ellipsoidAuthName, ellipsoidAuthCode );
1283  else
1284  {
1285  double semiMajor, semiMinor, invFlattening;
1286  int semiMinorComputed = 0;
1287  if ( proj_ellipsoid_get_parameters( QgsProjContext::get(), ellipsoid.get(), &semiMajor, &semiMinor, &semiMinorComputed, &invFlattening ) )
1288  {
1289  d->mEllipsoidAcronym = QStringLiteral( "PARAMETER:%1:%2" ).arg( qgsDoubleToString( semiMajor ),
1290  qgsDoubleToString( semiMinor ) );
1291  }
1292  else
1293  {
1294  d->mEllipsoidAcronym.clear();
1295  }
1296  }
1297  }
1298  }
1299  return d->mEllipsoidAcronym;
1300  }
1301  else
1302  {
1303  return d->mEllipsoidAcronym;
1304  }
1305 }
1306 
1308 {
1309  return toProj();
1310 }
1311 
1313 {
1314  if ( !d->mIsValid )
1315  return QString();
1316 
1317  if ( d->mProj4.isEmpty() )
1318  {
1319  if ( PJ *obj = d->threadLocalProjObject() )
1320  {
1321  d->mProj4 = getFullProjString( obj );
1322  }
1323  }
1324  // Stray spaces at the end?
1325  return d->mProj4.trimmed();
1326 }
1327 
1329 {
1330  // NOLINTBEGIN(bugprone-branch-clone)
1331  switch ( d->mProjType )
1332  {
1333  case PJ_TYPE_UNKNOWN:
1334  return Qgis::CrsType::Unknown;
1335 
1336  case PJ_TYPE_ELLIPSOID:
1337  case PJ_TYPE_PRIME_MERIDIAN:
1338  case PJ_TYPE_GEODETIC_REFERENCE_FRAME:
1339  case PJ_TYPE_DYNAMIC_GEODETIC_REFERENCE_FRAME:
1340  case PJ_TYPE_VERTICAL_REFERENCE_FRAME:
1341  case PJ_TYPE_DYNAMIC_VERTICAL_REFERENCE_FRAME:
1342  case PJ_TYPE_DATUM_ENSEMBLE:
1343  case PJ_TYPE_CONVERSION:
1344  case PJ_TYPE_TRANSFORMATION:
1345  case PJ_TYPE_CONCATENATED_OPERATION:
1346  case PJ_TYPE_OTHER_COORDINATE_OPERATION:
1347  case PJ_TYPE_TEMPORAL_DATUM:
1348  case PJ_TYPE_ENGINEERING_DATUM:
1349  case PJ_TYPE_PARAMETRIC_DATUM:
1350  return Qgis::CrsType::Other;
1351 
1352  case PJ_TYPE_CRS:
1353  case PJ_TYPE_GEOGRAPHIC_CRS:
1354  //not possible
1355  return Qgis::CrsType::Other;
1356 
1357  case PJ_TYPE_GEODETIC_CRS:
1358  return Qgis::CrsType::Geodetic;
1359  case PJ_TYPE_GEOCENTRIC_CRS:
1361  case PJ_TYPE_GEOGRAPHIC_2D_CRS:
1363  case PJ_TYPE_GEOGRAPHIC_3D_CRS:
1365  case PJ_TYPE_VERTICAL_CRS:
1366  return Qgis::CrsType::Vertical;
1367  case PJ_TYPE_PROJECTED_CRS:
1368  return Qgis::CrsType::Projected;
1369  case PJ_TYPE_COMPOUND_CRS:
1370  return Qgis::CrsType::Compound;
1371  case PJ_TYPE_TEMPORAL_CRS:
1372  return Qgis::CrsType::Temporal;
1373  case PJ_TYPE_ENGINEERING_CRS:
1375  case PJ_TYPE_BOUND_CRS:
1376  return Qgis::CrsType::Bound;
1377  case PJ_TYPE_OTHER_CRS:
1378  return Qgis::CrsType::Other;
1379 #if PROJ_VERSION_MAJOR>9 || (PROJ_VERSION_MAJOR==9 && PROJ_VERSION_MINOR>=2)
1380  case PJ_TYPE_DERIVED_PROJECTED_CRS:
1382  case PJ_TYPE_COORDINATE_METADATA:
1383  return Qgis::CrsType::Other;
1384 #endif
1385  }
1386  return Qgis::CrsType::Unknown;
1387  // NOLINTEND(bugprone-branch-clone)
1388 }
1389 
1391 {
1392  const PJ *pj = projObject();
1393  if ( !pj )
1394  return false;
1395 
1396  return proj_is_deprecated( pj );
1397 }
1398 
1400 {
1401  return d->mIsGeographic;
1402 }
1403 
1405 {
1406  const PJ *pj = projObject();
1407  if ( !pj )
1408  return false;
1409 
1410  return QgsProjUtils::isDynamic( pj );
1411 }
1412 
1414 {
1415  const PJ *pj = projObject();
1416  if ( !pj )
1417  return QString();
1418 
1419 #if PROJ_VERSION_MAJOR>8 || (PROJ_VERSION_MAJOR==8 && PROJ_VERSION_MINOR>=1)
1420  PJ_CONTEXT *context = QgsProjContext::get();
1421 
1422  return QString( proj_get_celestial_body_name( context, pj ) );
1423 #else
1424  throw QgsNotSupportedException( QObject::tr( "Retrieving celestial body requires a QGIS build based on PROJ 8.1 or later" ) );
1425 #endif
1426 }
1427 
1429 {
1430  if ( d->mCoordinateEpoch == epoch )
1431  return;
1432 
1433  // detaching clears the proj object, so we need to clone the existing one first
1435  d.detach();
1436  d->mCoordinateEpoch = epoch;
1437  d->setPj( std::move( clone ) );
1438 }
1439 
1441 {
1442  return d->mCoordinateEpoch;
1443 }
1444 
1446 {
1447  QgsDatumEnsemble res;
1448  res.mValid = false;
1449 
1450  const PJ *pj = projObject();
1451  if ( !pj )
1452  return res;
1453 
1454 #if PROJ_VERSION_MAJOR>=8
1455  PJ_CONTEXT *context = QgsProjContext::get();
1456 
1458  if ( !ensemble )
1459  return res;
1460 
1461  res.mValid = true;
1462  res.mName = QString( proj_get_name( ensemble.get() ) );
1463  res.mAuthority = QString( proj_get_id_auth_name( ensemble.get(), 0 ) );
1464  res.mCode = QString( proj_get_id_code( ensemble.get(), 0 ) );
1465  res.mRemarks = QString( proj_get_remarks( ensemble.get() ) );
1466  res.mScope = QString( proj_get_scope( ensemble.get() ) );
1467  res.mAccuracy = proj_datum_ensemble_get_accuracy( context, ensemble.get() );
1468 
1469  const int memberCount = proj_datum_ensemble_get_member_count( context, ensemble.get() );
1470  for ( int i = 0; i < memberCount; ++i )
1471  {
1472  QgsProjUtils::proj_pj_unique_ptr member( proj_datum_ensemble_get_member( context, ensemble.get(), i ) );
1473  if ( !member )
1474  continue;
1475 
1476  QgsDatumEnsembleMember details;
1477  details.mName = QString( proj_get_name( member.get() ) );
1478  details.mAuthority = QString( proj_get_id_auth_name( member.get(), 0 ) );
1479  details.mCode = QString( proj_get_id_code( member.get(), 0 ) );
1480  details.mRemarks = QString( proj_get_remarks( member.get() ) );
1481  details.mScope = QString( proj_get_scope( member.get() ) );
1482 
1483  res.mMembers << details;
1484  }
1485  return res;
1486 #else
1487  throw QgsNotSupportedException( QObject::tr( "Calculating datum ensembles requires a QGIS build based on PROJ 8.0 or later" ) );
1488 #endif
1489 }
1490 
1492 {
1494 
1495  // we have to make a transformation object corresponding to the crs
1496  QString projString = toProj();
1497  projString.replace( QLatin1String( "+type=crs" ), QString() );
1498 
1499  QgsProjUtils::proj_pj_unique_ptr transformation( proj_create( QgsProjContext::get(), projString.toUtf8().constData() ) );
1500  if ( !transformation )
1501  return res;
1502 
1503  PJ_COORD coord = proj_coord( 0, 0, 0, HUGE_VAL );
1504  coord.uv.u = point.x() * M_PI / 180.0;
1505  coord.uv.v = point.y() * M_PI / 180.0;
1506 
1507  proj_errno_reset( transformation.get() );
1508  const PJ_FACTORS pjFactors = proj_factors( transformation.get(), coord );
1509  if ( proj_errno( transformation.get() ) )
1510  {
1511  return res;
1512  }
1513 
1514  res.mIsValid = true;
1515  res.mMeridionalScale = pjFactors.meridional_scale;
1516  res.mParallelScale = pjFactors.parallel_scale;
1517  res.mArealScale = pjFactors.areal_scale;
1518  res.mAngularDistortion = pjFactors.angular_distortion;
1519  res.mMeridianParallelAngle = pjFactors.meridian_parallel_angle * 180 / M_PI;
1520  res.mMeridianConvergence = pjFactors.meridian_convergence * 180 / M_PI;
1521  res.mTissotSemimajor = pjFactors.tissot_semimajor;
1522  res.mTissotSemiminor = pjFactors.tissot_semiminor;
1523  res.mDxDlam = pjFactors.dx_dlam;
1524  res.mDxDphi = pjFactors.dx_dphi;
1525  res.mDyDlam = pjFactors.dy_dlam;
1526  res.mDyDphi = pjFactors.dy_dphi;
1527  return res;
1528 }
1529 
1531 {
1532  if ( !d->mIsValid )
1533  return QgsProjOperation();
1534 
1535  QgsProjOperation res;
1536 
1537  // we have to make a transformation object corresponding to the crs
1538  QString projString = toProj();
1539  projString.replace( QLatin1String( "+type=crs" ), QString() );
1540  if ( projString.isEmpty() )
1541  return QgsProjOperation();
1542 
1543  QgsProjUtils::proj_pj_unique_ptr transformation( proj_create( QgsProjContext::get(), projString.toUtf8().constData() ) );
1544  if ( !transformation )
1545  return res;
1546 
1547  PJ_PROJ_INFO info = proj_pj_info( transformation.get() );
1548 
1549  if ( info.id )
1550  {
1551  return QgsApplication::coordinateReferenceSystemRegistry()->projOperations().value( QString( info.id ) );
1552  }
1553 
1554  return res;
1555 }
1556 
1558 {
1559  if ( !d->mIsValid )
1561 
1562  return d->mMapUnits;
1563 }
1564 
1566 {
1567  if ( !d->mIsValid )
1568  return QgsRectangle();
1569 
1570  PJ *obj = d->threadLocalProjObject();
1571  if ( !obj )
1572  return QgsRectangle();
1573 
1574  double westLon = 0;
1575  double southLat = 0;
1576  double eastLon = 0;
1577  double northLat = 0;
1578 
1579  if ( !proj_get_area_of_use( QgsProjContext::get(), obj,
1580  &westLon, &southLat, &eastLon, &northLat, nullptr ) )
1581  return QgsRectangle();
1582 
1583 
1584  // don't use the constructor which normalizes!
1585  QgsRectangle rect;
1586  rect.setXMinimum( westLon );
1587  rect.setYMinimum( southLat );
1588  rect.setXMaximum( eastLon );
1589  rect.setYMaximum( northLat );
1590  return rect;
1591 }
1592 
1594 {
1595  const auto parts { authid().split( ':' ) };
1596  if ( parts.length() == 2 )
1597  {
1598  if ( parts[0] == QLatin1String( "EPSG" ) )
1599  return QStringLiteral( "http://www.opengis.net/def/crs/EPSG/0/%1" ).arg( parts[1] ) ;
1600  else if ( parts[0] == QLatin1String( "OGC" ) )
1601  {
1602  return QStringLiteral( "http://www.opengis.net/def/crs/OGC/1.3/%1" ).arg( parts[1] ) ;
1603  }
1604  else
1605  {
1606  QgsMessageLog::logMessage( QStringLiteral( "Error converting published CRS to URI %1: (not OGC or EPSG)" ).arg( authid() ), QStringLiteral( "CRS" ), Qgis::MessageLevel::Critical );
1607  }
1608  }
1609  else
1610  {
1611  QgsMessageLog::logMessage( QStringLiteral( "Error converting published CRS to URI: %1" ).arg( authid() ), QStringLiteral( "CRS" ), Qgis::MessageLevel::Critical );
1612  }
1613  return QString();
1614 }
1615 
1617 {
1618  if ( !d->mIsValid )
1619  return;
1620 
1621  if ( d->mSrsId >= USER_CRS_START_ID )
1622  {
1623  // user CRS, so update to new definition
1624  createFromSrsId( d->mSrsId );
1625  }
1626  else
1627  {
1628  // nothing to do -- only user CRS definitions can be changed
1629  }
1630 }
1631 
1632 void QgsCoordinateReferenceSystem::setProjString( const QString &proj4String )
1633 {
1634  d.detach();
1635  d->mProj4 = proj4String;
1636  d->mWktPreferred.clear();
1637 
1638  QgsLocaleNumC l;
1639  QString trimmed = proj4String.trimmed();
1640 
1641  trimmed += QLatin1String( " +type=crs" );
1643 
1644  {
1645  d->setPj( QgsProjUtils::proj_pj_unique_ptr( proj_create( ctx, trimmed.toLatin1().constData() ) ) );
1646  }
1647 
1648  if ( !d->hasPj() )
1649  {
1650 #ifdef QGISDEBUG
1651  const int errNo = proj_context_errno( ctx );
1652  QgsDebugError( QStringLiteral( "proj string rejected: %1" ).arg( proj_errno_string( errNo ) ) );
1653 #endif
1654  d->mIsValid = false;
1655  }
1656  else
1657  {
1658  d->mEllipsoidAcronym.clear();
1659  d->mIsValid = true;
1660  }
1661 
1662  setMapUnits();
1663 }
1664 
1665 bool QgsCoordinateReferenceSystem::setWktString( const QString &wkt )
1666 {
1667  bool res = false;
1668  d->mIsValid = false;
1669  d->mWktPreferred.clear();
1670 
1671  PROJ_STRING_LIST warnings = nullptr;
1672  PROJ_STRING_LIST grammarErrors = nullptr;
1673  {
1674  d->setPj( QgsProjUtils::proj_pj_unique_ptr( proj_create_from_wkt( QgsProjContext::get(), wkt.toLatin1().constData(), nullptr, &warnings, &grammarErrors ) ) );
1675  }
1676 
1677  res = d->hasPj();
1678  if ( !res )
1679  {
1680  QgsDebugMsgLevel( QStringLiteral( "\n---------------------------------------------------------------" ), 2 );
1681  QgsDebugMsgLevel( QStringLiteral( "This CRS could *** NOT *** be set from the supplied Wkt " ), 2 );
1682  QgsDebugMsgLevel( "INPUT: " + wkt, 2 );
1683  for ( auto iter = warnings; iter && *iter; ++iter )
1684  QgsDebugMsgLevel( *iter, 2 );
1685  for ( auto iter = grammarErrors; iter && *iter; ++iter )
1686  QgsDebugMsgLevel( *iter, 2 );
1687  QgsDebugMsgLevel( QStringLiteral( "---------------------------------------------------------------\n" ), 2 );
1688  }
1689  proj_string_list_destroy( warnings );
1690  proj_string_list_destroy( grammarErrors );
1691 
1692  QgsReadWriteLocker locker( *sProj4CacheLock(), QgsReadWriteLocker::Unlocked );
1693  if ( !res )
1694  {
1695  locker.changeMode( QgsReadWriteLocker::Write );
1696  if ( !sDisableWktCache )
1697  sWktCache()->insert( wkt, *this );
1698  return d->mIsValid;
1699  }
1700 
1701  if ( d->hasPj() )
1702  {
1703  // try 1 - maybe we can directly grab the auth name and code from the crs already?
1704  QString authName( proj_get_id_auth_name( d->threadLocalProjObject(), 0 ) );
1705  QString authCode( proj_get_id_code( d->threadLocalProjObject(), 0 ) );
1706 
1707  if ( authName.isEmpty() || authCode.isEmpty() )
1708  {
1709  // try 2, use proj's identify method and see if there's a nice candidate we can use
1710  QgsProjUtils::identifyCrs( d->threadLocalProjObject(), authName, authCode );
1711  }
1712 
1713  if ( !authName.isEmpty() && !authCode.isEmpty() )
1714  {
1715  if ( loadFromAuthCode( authName, authCode ) )
1716  {
1717  locker.changeMode( QgsReadWriteLocker::Write );
1718  if ( !sDisableWktCache )
1719  sWktCache()->insert( wkt, *this );
1720  return d->mIsValid;
1721  }
1722  }
1723  else
1724  {
1725  // Still a valid CRS, just not a known one
1726  d->mIsValid = true;
1727  d->mDescription = QString( proj_get_name( d->threadLocalProjObject() ) );
1728  }
1729  setMapUnits();
1730  }
1731 
1732  return d->mIsValid;
1733 }
1734 
1735 void QgsCoordinateReferenceSystem::setMapUnits()
1736 {
1737  if ( !d->mIsValid )
1738  {
1739  d->mMapUnits = Qgis::DistanceUnit::Unknown;
1740  return;
1741  }
1742 
1743  if ( !d->hasPj() )
1744  {
1745  d->mMapUnits = Qgis::DistanceUnit::Unknown;
1746  return;
1747  }
1748 
1749  PJ_CONTEXT *context = QgsProjContext::get();
1750  // prefer horizontal CRS units, if present
1752  if ( !crs )
1753  crs = QgsProjUtils::unboundCrs( d->threadLocalProjObject() );
1754 
1755  if ( !crs )
1756  {
1757  d->mMapUnits = Qgis::DistanceUnit::Unknown;
1758  return;
1759  }
1760 
1761  QgsProjUtils::proj_pj_unique_ptr coordinateSystem( proj_crs_get_coordinate_system( context, crs.get() ) );
1762  if ( !coordinateSystem )
1763  {
1764  d->mMapUnits = Qgis::DistanceUnit::Unknown;
1765  return;
1766  }
1767 
1768  const int axisCount = proj_cs_get_axis_count( context, coordinateSystem.get() );
1769  if ( axisCount > 0 )
1770  {
1771  const char *outUnitName = nullptr;
1772  // Read only first axis
1773  proj_cs_get_axis_info( context, coordinateSystem.get(), 0,
1774  nullptr,
1775  nullptr,
1776  nullptr,
1777  nullptr,
1778  &outUnitName,
1779  nullptr,
1780  nullptr );
1781 
1782  const QString unitName( outUnitName );
1783 
1784  // proj unit names are freeform -- they differ from authority to authority :(
1785  // see https://lists.osgeo.org/pipermail/proj/2019-April/008444.html
1786  if ( unitName.compare( QLatin1String( "degree" ), Qt::CaseInsensitive ) == 0 ||
1787  unitName.compare( QLatin1String( "degree minute second" ), Qt::CaseInsensitive ) == 0 ||
1788  unitName.compare( QLatin1String( "degree minute second hemisphere" ), Qt::CaseInsensitive ) == 0 ||
1789  unitName.compare( QLatin1String( "degree minute" ), Qt::CaseInsensitive ) == 0 ||
1790  unitName.compare( QLatin1String( "degree hemisphere" ), Qt::CaseInsensitive ) == 0 ||
1791  unitName.compare( QLatin1String( "degree minute hemisphere" ), Qt::CaseInsensitive ) == 0 ||
1792  unitName.compare( QLatin1String( "hemisphere degree" ), Qt::CaseInsensitive ) == 0 ||
1793  unitName.compare( QLatin1String( "hemisphere degree minute" ), Qt::CaseInsensitive ) == 0 ||
1794  unitName.compare( QLatin1String( "hemisphere degree minute second" ), Qt::CaseInsensitive ) == 0 ||
1795  unitName.compare( QLatin1String( "degree (supplier to define representation)" ), Qt::CaseInsensitive ) == 0 )
1796  d->mMapUnits = Qgis::DistanceUnit::Degrees;
1797  else if ( unitName.compare( QLatin1String( "metre" ), Qt::CaseInsensitive ) == 0
1798  || unitName.compare( QLatin1String( "m" ), Qt::CaseInsensitive ) == 0
1799  || unitName.compare( QLatin1String( "meter" ), Qt::CaseInsensitive ) == 0 )
1800  d->mMapUnits = Qgis::DistanceUnit::Meters;
1801  // we don't differentiate between these, suck it imperial users!
1802  else if ( unitName.compare( QLatin1String( "US survey foot" ), Qt::CaseInsensitive ) == 0 ||
1803  unitName.compare( QLatin1String( "foot" ), Qt::CaseInsensitive ) == 0 )
1804  d->mMapUnits = Qgis::DistanceUnit::Feet;
1805  else if ( unitName.compare( QLatin1String( "kilometre" ), Qt::CaseInsensitive ) == 0 ) //#spellok
1806  d->mMapUnits = Qgis::DistanceUnit::Kilometers;
1807  else if ( unitName.compare( QLatin1String( "centimetre" ), Qt::CaseInsensitive ) == 0 ) //#spellok
1808  d->mMapUnits = Qgis::DistanceUnit::Centimeters;
1809  else if ( unitName.compare( QLatin1String( "millimetre" ), Qt::CaseInsensitive ) == 0 ) //#spellok
1810  d->mMapUnits = Qgis::DistanceUnit::Millimeters;
1811  else if ( unitName.compare( QLatin1String( "Statute mile" ), Qt::CaseInsensitive ) == 0 )
1812  d->mMapUnits = Qgis::DistanceUnit::Miles;
1813  else if ( unitName.compare( QLatin1String( "nautical mile" ), Qt::CaseInsensitive ) == 0 )
1814  d->mMapUnits = Qgis::DistanceUnit::NauticalMiles;
1815  else if ( unitName.compare( QLatin1String( "yard" ), Qt::CaseInsensitive ) == 0 )
1816  d->mMapUnits = Qgis::DistanceUnit::Yards;
1817  // TODO - maybe more values to handle here?
1818  else
1819  d->mMapUnits = Qgis::DistanceUnit::Unknown;
1820  return;
1821  }
1822  else
1823  {
1824  d->mMapUnits = Qgis::DistanceUnit::Unknown;
1825  return;
1826  }
1827 }
1828 
1829 
1831 {
1832  if ( d->mEllipsoidAcronym.isNull() || d->mProjectionAcronym.isNull()
1833  || !d->mIsValid )
1834  {
1835  QgsDebugMsgLevel( "QgsCoordinateReferenceSystem::findMatchingProj will only "
1836  "work if prj acr ellipsoid acr and proj4string are set"
1837  " and the current projection is valid!", 4 );
1838  return 0;
1839  }
1840 
1841  sqlite3_database_unique_ptr database;
1842  sqlite3_statement_unique_ptr statement;
1843  int myResult;
1844 
1845  // Set up the query to retrieve the projection information
1846  // needed to populate the list
1847  QString mySql = QString( "select srs_id,parameters from tbl_srs where "
1848  "projection_acronym=%1 and ellipsoid_acronym=%2 order by deprecated" )
1849  .arg( QgsSqliteUtils::quotedString( d->mProjectionAcronym ),
1850  QgsSqliteUtils::quotedString( d->mEllipsoidAcronym ) );
1851  // Get the full path name to the sqlite3 spatial reference database.
1852  QString myDatabaseFileName = QgsApplication::srsDatabaseFilePath();
1853 
1854  //check the db is available
1855  myResult = openDatabase( myDatabaseFileName, database );
1856  if ( myResult != SQLITE_OK )
1857  {
1858  return 0;
1859  }
1860 
1861  statement = database.prepare( mySql, myResult );
1862  if ( myResult == SQLITE_OK )
1863  {
1864 
1865  while ( statement.step() == SQLITE_ROW )
1866  {
1867  QString mySrsId = statement.columnAsText( 0 );
1868  QString myProj4String = statement.columnAsText( 1 );
1869  if ( toProj() == myProj4String.trimmed() )
1870  {
1871  return mySrsId.toLong();
1872  }
1873  }
1874  }
1875 
1876  //
1877  // Try the users db now
1878  //
1879 
1880  myDatabaseFileName = QgsApplication::qgisUserDatabaseFilePath();
1881  //check the db is available
1882  myResult = openDatabase( myDatabaseFileName, database );
1883  if ( myResult != SQLITE_OK )
1884  {
1885  return 0;
1886  }
1887 
1888  statement = database.prepare( mySql, myResult );
1889 
1890  if ( myResult == SQLITE_OK )
1891  {
1892  while ( statement.step() == SQLITE_ROW )
1893  {
1894  QString mySrsId = statement.columnAsText( 0 );
1895  QString myProj4String = statement.columnAsText( 1 );
1896  if ( toProj() == myProj4String.trimmed() )
1897  {
1898  return mySrsId.toLong();
1899  }
1900  }
1901  }
1902 
1903  return 0;
1904 }
1905 
1907 {
1908  // shortcut
1909  if ( d == srs.d )
1910  return true;
1911 
1912  if ( !d->mIsValid && !srs.d->mIsValid )
1913  return true;
1914 
1915  if ( !d->mIsValid || !srs.d->mIsValid )
1916  return false;
1917 
1918  if ( !qgsNanCompatibleEquals( d->mCoordinateEpoch, srs.d->mCoordinateEpoch ) )
1919  return false;
1920 
1921  const bool isUser = d->mSrsId >= USER_CRS_START_ID;
1922  const bool otherIsUser = srs.d->mSrsId >= USER_CRS_START_ID;
1923  if ( isUser != otherIsUser )
1924  return false;
1925 
1926  // we can't directly compare authid for user crses -- the actual definition of these may have changed
1927  if ( !isUser && ( !d->mAuthId.isEmpty() || !srs.d->mAuthId.isEmpty() ) )
1928  return d->mAuthId == srs.d->mAuthId;
1929 
1931 }
1932 
1934 {
1935  return !( *this == srs );
1936 }
1937 
1938 QString QgsCoordinateReferenceSystem::toWkt( Qgis::CrsWktVariant variant, bool multiline, int indentationWidth ) const
1939 {
1940  if ( PJ *obj = d->threadLocalProjObject() )
1941  {
1942  const bool isDefaultPreferredFormat = variant == Qgis::CrsWktVariant::Preferred && !multiline;
1943  if ( isDefaultPreferredFormat && !d->mWktPreferred.isEmpty() )
1944  {
1945  // can use cached value
1946  return d->mWktPreferred;
1947  }
1948 
1949  PJ_WKT_TYPE type = PJ_WKT1_GDAL;
1950  switch ( variant )
1951  {
1953  type = PJ_WKT1_GDAL;
1954  break;
1956  type = PJ_WKT1_ESRI;
1957  break;
1959  type = PJ_WKT2_2015;
1960  break;
1962  type = PJ_WKT2_2015_SIMPLIFIED;
1963  break;
1965  type = PJ_WKT2_2019;
1966  break;
1968  type = PJ_WKT2_2019_SIMPLIFIED;
1969  break;
1970  }
1971 
1972  const QByteArray multiLineOption = QStringLiteral( "MULTILINE=%1" ).arg( multiline ? QStringLiteral( "YES" ) : QStringLiteral( "NO" ) ).toLocal8Bit();
1973  const QByteArray indentatationWidthOption = QStringLiteral( "INDENTATION_WIDTH=%1" ).arg( multiline ? QString::number( indentationWidth ) : QStringLiteral( "0" ) ).toLocal8Bit();
1974  const char *const options[] = {multiLineOption.constData(), indentatationWidthOption.constData(), nullptr};
1975  QString res = proj_as_wkt( QgsProjContext::get(), obj, type, options );
1976 
1977  if ( isDefaultPreferredFormat )
1978  {
1979  // cache result for later use
1980  d->mWktPreferred = res;
1981  }
1982 
1983  return res;
1984  }
1985  return QString();
1986 }
1987 
1988 bool QgsCoordinateReferenceSystem::readXml( const QDomNode &node )
1989 {
1990  d.detach();
1991  bool result = true;
1992  QDomNode srsNode = node.namedItem( QStringLiteral( "spatialrefsys" ) );
1993 
1994  if ( ! srsNode.isNull() )
1995  {
1996  bool initialized = false;
1997 
1998  bool ok = false;
1999  long srsid = srsNode.namedItem( QStringLiteral( "srsid" ) ).toElement().text().toLong( &ok );
2000 
2001  QDomNode node;
2002 
2003  if ( ok && srsid > 0 && srsid < USER_CRS_START_ID )
2004  {
2005  node = srsNode.namedItem( QStringLiteral( "authid" ) );
2006  if ( !node.isNull() )
2007  {
2008  createFromOgcWmsCrs( node.toElement().text() );
2009  if ( isValid() )
2010  {
2011  initialized = true;
2012  }
2013  }
2014 
2015  if ( !initialized )
2016  {
2017  node = srsNode.namedItem( QStringLiteral( "epsg" ) );
2018  if ( !node.isNull() )
2019  {
2020  operator=( QgsCoordinateReferenceSystem::fromEpsgId( node.toElement().text().toLong() ) );
2021  if ( isValid() )
2022  {
2023  initialized = true;
2024  }
2025  }
2026  }
2027  }
2028 
2029  // if wkt is present, prefer that since it's lossless (unlike proj4 strings)
2030  if ( !initialized )
2031  {
2032  // before doing anything, we grab and set the stored CRS name (description).
2033  // this way if the stored CRS doesn't match anything available locally (i.e. from Proj's db
2034  // or the user's custom CRS list), then we will correctly show the CRS with its original
2035  // name (instead of just "custom crs")
2036  const QString description = srsNode.namedItem( QStringLiteral( "description" ) ).toElement().text();
2037 
2038  const QString wkt = srsNode.namedItem( QStringLiteral( "wkt" ) ).toElement().text();
2039  initialized = createFromWktInternal( wkt, description );
2040  }
2041 
2042  if ( !initialized )
2043  {
2044  node = srsNode.namedItem( QStringLiteral( "proj4" ) );
2045  const QString proj4 = node.toElement().text();
2046  initialized = createFromProj( proj4 );
2047  }
2048 
2049  if ( !initialized )
2050  {
2051  // Setting from elements one by one
2052  node = srsNode.namedItem( QStringLiteral( "proj4" ) );
2053  const QString proj4 = node.toElement().text();
2054  if ( !proj4.trimmed().isEmpty() )
2055  setProjString( node.toElement().text() );
2056 
2057  node = srsNode.namedItem( QStringLiteral( "srsid" ) );
2058  d->mSrsId = node.toElement().text().toLong();
2059 
2060  node = srsNode.namedItem( QStringLiteral( "srid" ) );
2061  d->mSRID = node.toElement().text().toLong();
2062 
2063  node = srsNode.namedItem( QStringLiteral( "authid" ) );
2064  d->mAuthId = node.toElement().text();
2065 
2066  node = srsNode.namedItem( QStringLiteral( "description" ) );
2067  d->mDescription = node.toElement().text();
2068 
2069  node = srsNode.namedItem( QStringLiteral( "projectionacronym" ) );
2070  d->mProjectionAcronym = node.toElement().text();
2071 
2072  node = srsNode.namedItem( QStringLiteral( "ellipsoidacronym" ) );
2073  d->mEllipsoidAcronym = node.toElement().text();
2074 
2075  node = srsNode.namedItem( QStringLiteral( "geographicflag" ) );
2076  d->mIsGeographic = node.toElement().text() == QLatin1String( "true" );
2077 
2078  d->mWktPreferred.clear();
2079 
2080  //make sure the map units have been set
2081  setMapUnits();
2082  }
2083 
2084  const QString epoch = srsNode.toElement().attribute( QStringLiteral( "coordinateEpoch" ) );
2085  if ( !epoch.isEmpty() )
2086  {
2087  bool epochOk = false;
2088  d->mCoordinateEpoch = epoch.toDouble( &epochOk );
2089  if ( !epochOk )
2090  d->mCoordinateEpoch = std::numeric_limits< double >::quiet_NaN();
2091  }
2092  else
2093  {
2094  d->mCoordinateEpoch = std::numeric_limits< double >::quiet_NaN();
2095  }
2096 
2097  mNativeFormat = qgsEnumKeyToValue<Qgis::CrsDefinitionFormat>( srsNode.toElement().attribute( QStringLiteral( "nativeFormat" ) ), Qgis::CrsDefinitionFormat::Wkt );
2098  }
2099  else
2100  {
2101  // Return empty CRS if none was found in the XML.
2102  d = new QgsCoordinateReferenceSystemPrivate();
2103  result = false;
2104  }
2105  return result;
2106 }
2107 
2108 bool QgsCoordinateReferenceSystem::writeXml( QDomNode &node, QDomDocument &doc ) const
2109 {
2110  QDomElement layerNode = node.toElement();
2111  QDomElement srsElement = doc.createElement( QStringLiteral( "spatialrefsys" ) );
2112 
2113  srsElement.setAttribute( QStringLiteral( "nativeFormat" ), qgsEnumValueToKey<Qgis::CrsDefinitionFormat>( mNativeFormat ) );
2114 
2115  if ( std::isfinite( d->mCoordinateEpoch ) )
2116  {
2117  srsElement.setAttribute( QStringLiteral( "coordinateEpoch" ), d->mCoordinateEpoch );
2118  }
2119 
2120  QDomElement wktElement = doc.createElement( QStringLiteral( "wkt" ) );
2121  wktElement.appendChild( doc.createTextNode( toWkt( Qgis::CrsWktVariant::Preferred ) ) );
2122  srsElement.appendChild( wktElement );
2123 
2124  QDomElement proj4Element = doc.createElement( QStringLiteral( "proj4" ) );
2125  proj4Element.appendChild( doc.createTextNode( toProj() ) );
2126  srsElement.appendChild( proj4Element );
2127 
2128  QDomElement srsIdElement = doc.createElement( QStringLiteral( "srsid" ) );
2129  srsIdElement.appendChild( doc.createTextNode( QString::number( srsid() ) ) );
2130  srsElement.appendChild( srsIdElement );
2131 
2132  QDomElement sridElement = doc.createElement( QStringLiteral( "srid" ) );
2133  sridElement.appendChild( doc.createTextNode( QString::number( postgisSrid() ) ) );
2134  srsElement.appendChild( sridElement );
2135 
2136  QDomElement authidElement = doc.createElement( QStringLiteral( "authid" ) );
2137  authidElement.appendChild( doc.createTextNode( authid() ) );
2138  srsElement.appendChild( authidElement );
2139 
2140  QDomElement descriptionElement = doc.createElement( QStringLiteral( "description" ) );
2141  descriptionElement.appendChild( doc.createTextNode( description() ) );
2142  srsElement.appendChild( descriptionElement );
2143 
2144  QDomElement projectionAcronymElement = doc.createElement( QStringLiteral( "projectionacronym" ) );
2145  projectionAcronymElement.appendChild( doc.createTextNode( projectionAcronym() ) );
2146  srsElement.appendChild( projectionAcronymElement );
2147 
2148  QDomElement ellipsoidAcronymElement = doc.createElement( QStringLiteral( "ellipsoidacronym" ) );
2149  ellipsoidAcronymElement.appendChild( doc.createTextNode( ellipsoidAcronym() ) );
2150  srsElement.appendChild( ellipsoidAcronymElement );
2151 
2152  QDomElement geographicFlagElement = doc.createElement( QStringLiteral( "geographicflag" ) );
2153  QString geoFlagText = QStringLiteral( "false" );
2154  if ( isGeographic() )
2155  {
2156  geoFlagText = QStringLiteral( "true" );
2157  }
2158 
2159  geographicFlagElement.appendChild( doc.createTextNode( geoFlagText ) );
2160  srsElement.appendChild( geographicFlagElement );
2161 
2162  layerNode.appendChild( srsElement );
2163 
2164  return true;
2165 }
2166 
2167 //
2168 // Static helper methods below this point only please!
2169 //
2170 
2171 
2172 // Returns the whole proj4 string for the selected srsid
2173 //this is a static method! NOTE I've made it private for now to reduce API clutter TS
2174 QString QgsCoordinateReferenceSystem::projFromSrsId( const int srsId )
2175 {
2176  QString myDatabaseFileName;
2177  QString myProjString;
2178  QString mySql = QStringLiteral( "select parameters from tbl_srs where srs_id = %1 order by deprecated" ).arg( srsId );
2179 
2180  //
2181  // Determine if this is a user projection or a system on
2182  // user projection defs all have srs_id >= 100000
2183  //
2184  if ( srsId >= USER_CRS_START_ID )
2185  {
2186  myDatabaseFileName = QgsApplication::qgisUserDatabaseFilePath();
2187  QFileInfo myFileInfo;
2188  myFileInfo.setFile( myDatabaseFileName );
2189  if ( !myFileInfo.exists() ) //its unlikely that this condition will ever be reached
2190  {
2191  QgsDebugError( QStringLiteral( "users qgis.db not found" ) );
2192  return QString();
2193  }
2194  }
2195  else //must be a system projection then
2196  {
2197  myDatabaseFileName = QgsApplication::srsDatabaseFilePath();
2198  }
2199 
2200  sqlite3_database_unique_ptr database;
2201  sqlite3_statement_unique_ptr statement;
2202 
2203  int rc;
2204  rc = openDatabase( myDatabaseFileName, database );
2205  if ( rc )
2206  {
2207  return QString();
2208  }
2209 
2210  statement = database.prepare( mySql, rc );
2211 
2212  if ( rc == SQLITE_OK )
2213  {
2214  if ( statement.step() == SQLITE_ROW )
2215  {
2216  myProjString = statement.columnAsText( 0 );
2217  }
2218  }
2219 
2220  return myProjString;
2221 }
2222 
2223 int QgsCoordinateReferenceSystem::openDatabase( const QString &path, sqlite3_database_unique_ptr &database, bool readonly )
2224 {
2225  int myResult;
2226  if ( readonly )
2227  myResult = database.open_v2( path, SQLITE_OPEN_READONLY, nullptr );
2228  else
2229  myResult = database.open( path );
2230 
2231  if ( myResult != SQLITE_OK )
2232  {
2233  QgsDebugError( "Can't open database: " + database.errorMessage() );
2234  // XXX This will likely never happen since on open, sqlite creates the
2235  // database if it does not exist.
2236  // ... unfortunately it happens on Windows
2237  QgsMessageLog::logMessage( QObject::tr( "Could not open CRS database %1\nError(%2): %3" )
2238  .arg( path )
2239  .arg( myResult )
2240  .arg( database.errorMessage() ), QObject::tr( "CRS" ) );
2241  }
2242  return myResult;
2243 }
2244 
2246 {
2247  sCustomSrsValidation = f;
2248 }
2249 
2251 {
2252  return sCustomSrsValidation;
2253 }
2254 
2255 void QgsCoordinateReferenceSystem::debugPrint()
2256 {
2257  QgsDebugMsgLevel( QStringLiteral( "***SpatialRefSystem***" ), 1 );
2258  QgsDebugMsgLevel( "* Valid : " + ( d->mIsValid ? QString( "true" ) : QString( "false" ) ), 1 );
2259  QgsDebugMsgLevel( "* SrsId : " + QString::number( d->mSrsId ), 1 );
2260  QgsDebugMsgLevel( "* Proj4 : " + toProj(), 1 );
2262  QgsDebugMsgLevel( "* Desc. : " + d->mDescription, 1 );
2264  {
2265  QgsDebugMsgLevel( QStringLiteral( "* Units : meters" ), 1 );
2266  }
2267  else if ( mapUnits() == Qgis::DistanceUnit::Feet )
2268  {
2269  QgsDebugMsgLevel( QStringLiteral( "* Units : feet" ), 1 );
2270  }
2271  else if ( mapUnits() == Qgis::DistanceUnit::Degrees )
2272  {
2273  QgsDebugMsgLevel( QStringLiteral( "* Units : degrees" ), 1 );
2274  }
2275 }
2276 
2278 {
2279  mValidationHint = html;
2280 }
2281 
2283 {
2284  return mValidationHint;
2285 }
2286 
2288 {
2290 }
2291 
2293 {
2294  mNativeFormat = format;
2295 }
2296 
2298 {
2299  return mNativeFormat;
2300 }
2301 
2302 long QgsCoordinateReferenceSystem::getRecordCount()
2303 {
2304  sqlite3_database_unique_ptr database;
2305  sqlite3_statement_unique_ptr statement;
2306  int myResult;
2307  long myRecordCount = 0;
2308  //check the db is available
2309  myResult = database.open_v2( QgsApplication::qgisUserDatabaseFilePath(), SQLITE_OPEN_READONLY, nullptr );
2310  if ( myResult != SQLITE_OK )
2311  {
2312  QgsDebugError( QStringLiteral( "Can't open database: %1" ).arg( database.errorMessage() ) );
2313  return 0;
2314  }
2315  // Set up the query to retrieve the projection information needed to populate the ELLIPSOID list
2316  QString mySql = QStringLiteral( "select count(*) from tbl_srs" );
2317  statement = database.prepare( mySql, myResult );
2318  if ( myResult == SQLITE_OK )
2319  {
2320  if ( statement.step() == SQLITE_ROW )
2321  {
2322  QString myRecordCountString = statement.columnAsText( 0 );
2323  myRecordCount = myRecordCountString.toLong();
2324  }
2325  }
2326  return myRecordCount;
2327 }
2328 
2330 {
2331  PJ_CONTEXT *pjContext = QgsProjContext::get();
2332  bool isGeographic = false;
2333 
2334  // check horizontal CRS units
2336  if ( !horizontalCrs )
2337  return false;
2338 
2339  QgsProjUtils::proj_pj_unique_ptr coordinateSystem( proj_crs_get_coordinate_system( pjContext, horizontalCrs.get() ) );
2340  if ( coordinateSystem )
2341  {
2342  const int axisCount = proj_cs_get_axis_count( pjContext, coordinateSystem.get() );
2343  if ( axisCount > 0 )
2344  {
2345  const char *outUnitAuthName = nullptr;
2346  const char *outUnitAuthCode = nullptr;
2347  // Read only first axis
2348  proj_cs_get_axis_info( pjContext, coordinateSystem.get(), 0,
2349  nullptr,
2350  nullptr,
2351  nullptr,
2352  nullptr,
2353  nullptr,
2354  &outUnitAuthName,
2355  &outUnitAuthCode );
2356 
2357  if ( outUnitAuthName && outUnitAuthCode )
2358  {
2359  const char *unitCategory = nullptr;
2360  if ( proj_uom_get_info_from_database( pjContext, outUnitAuthName, outUnitAuthCode, nullptr, nullptr, &unitCategory ) )
2361  {
2362  isGeographic = QString( unitCategory ).compare( QLatin1String( "angular" ), Qt::CaseInsensitive ) == 0;
2363  }
2364  }
2365  }
2366  }
2367  return isGeographic;
2368 }
2369 
2370 void getOperationAndEllipsoidFromProjString( const QString &proj, QString &operation, QString &ellipsoid )
2371 {
2372  thread_local const QRegularExpression projRegExp( QStringLiteral( "\\+proj=(\\S+)" ) );
2373  const QRegularExpressionMatch projMatch = projRegExp.match( proj );
2374  if ( !projMatch.hasMatch() )
2375  {
2376  QgsDebugMsgLevel( QStringLiteral( "no +proj argument found [%2]" ).arg( proj ), 2 );
2377  return;
2378  }
2379  operation = projMatch.captured( 1 );
2380 
2381  const QRegularExpressionMatch ellipseMatch = projRegExp.match( proj );
2382  if ( ellipseMatch.hasMatch() )
2383  {
2384  ellipsoid = ellipseMatch.captured( 1 );
2385  }
2386  else
2387  {
2388  // satisfy not null constraint on ellipsoid_acronym field
2389  // possibly we should drop the constraint, yet the crses with missing ellipsoid_acronym are malformed
2390  // and will result in oddities within other areas of QGIS (e.g. project ellipsoid won't be correctly
2391  // set for these CRSes). Better just hack around and make the constraint happy for now,
2392  // and hope that the definitions get corrected in future.
2393  ellipsoid = "";
2394  }
2395 }
2396 
2397 
2398 bool QgsCoordinateReferenceSystem::loadFromAuthCode( const QString &auth, const QString &code )
2399 {
2400  if ( !QgsApplication::coordinateReferenceSystemRegistry()->authorities().contains( auth.toLower() ) )
2401  return false;
2402 
2403  d.detach();
2404  d->mIsValid = false;
2405  d->mWktPreferred.clear();
2406 
2407  PJ_CONTEXT *pjContext = QgsProjContext::get();
2408  QgsProjUtils::proj_pj_unique_ptr crs( proj_create_from_database( pjContext, auth.toUtf8().constData(), code.toUtf8().constData(), PJ_CATEGORY_CRS, false, nullptr ) );
2409  if ( !crs )
2410  {
2411  return false;
2412  }
2413 
2414  crs = QgsProjUtils::unboundCrs( crs.get() );
2415 
2416  QString proj4 = getFullProjString( crs.get() );
2417  proj4.replace( QLatin1String( "+type=crs" ), QString() );
2418  proj4 = proj4.trimmed();
2419 
2420  d->mIsValid = true;
2421  d->mProj4 = proj4;
2422  d->mWktPreferred.clear();
2423  d->mDescription = QString( proj_get_name( crs.get() ) );
2424  d->mAuthId = QStringLiteral( "%1:%2" ).arg( auth, code );
2425  d->mIsGeographic = testIsGeographic( crs.get() );
2426  d->mAxisInvertedDirty = true;
2427  QString operation;
2428  QString ellipsoid;
2430  d->mProjectionAcronym = operation;
2431  d->mEllipsoidAcronym.clear();
2432  d->setPj( std::move( crs ) );
2433 
2434  const QString dbVals = sAuthIdToQgisSrsIdMap.value( QStringLiteral( "%1:%2" ).arg( auth, code ).toUpper() );
2435  if ( !dbVals.isEmpty() )
2436  {
2437  const QStringList parts = dbVals.split( ',' );
2438  d->mSrsId = parts.at( 0 ).toInt();
2439  d->mSRID = parts.at( 1 ).toInt();
2440  }
2441 
2442  setMapUnits();
2443 
2444  return true;
2445 }
2446 
2447 QList<long> QgsCoordinateReferenceSystem::userSrsIds()
2448 {
2449  QList<long> results;
2450  // check user defined projection database
2451  const QString db = QgsApplication::qgisUserDatabaseFilePath();
2452 
2453  QFileInfo myInfo( db );
2454  if ( !myInfo.exists() )
2455  {
2456  QgsDebugError( "failed : " + db + " does not exist!" );
2457  return results;
2458  }
2459 
2460  sqlite3_database_unique_ptr database;
2461  sqlite3_statement_unique_ptr statement;
2462 
2463  //check the db is available
2464  int result = openDatabase( db, database );
2465  if ( result != SQLITE_OK )
2466  {
2467  QgsDebugError( "failed : " + db + " could not be opened!" );
2468  return results;
2469  }
2470 
2471  QString sql = QStringLiteral( "select srs_id from tbl_srs where srs_id >= %1" ).arg( USER_CRS_START_ID );
2472  int rc;
2473  statement = database.prepare( sql, rc );
2474  while ( true )
2475  {
2476  int ret = statement.step();
2477 
2478  if ( ret == SQLITE_DONE )
2479  {
2480  // there are no more rows to fetch - we can stop looping
2481  break;
2482  }
2483 
2484  if ( ret == SQLITE_ROW )
2485  {
2486  results.append( statement.columnAsInt64( 0 ) );
2487  }
2488  else
2489  {
2490  QgsMessageLog::logMessage( QObject::tr( "SQLite error: %2\nSQL: %1" ).arg( sql, sqlite3_errmsg( database.get() ) ), QObject::tr( "SpatiaLite" ) );
2491  break;
2492  }
2493  }
2494 
2495  return results;
2496 }
2497 
2498 long QgsCoordinateReferenceSystem::matchToUserCrs() const
2499 {
2500  PJ *obj = d->threadLocalProjObject();
2501  if ( !obj )
2502  return 0;
2503 
2504  const QList< long > ids = userSrsIds();
2505  for ( long id : ids )
2506  {
2508  if ( candidate.projObject() && proj_is_equivalent_to( obj, candidate.projObject(), PJ_COMP_EQUIVALENT ) )
2509  {
2510  return id;
2511  }
2512  }
2513  return 0;
2514 }
2515 
2516 static void sync_db_proj_logger( void * /* user_data */, int level, const char *message )
2517 {
2518 #ifndef QGISDEBUG
2519  Q_UNUSED( message )
2520 #endif
2521  if ( level == PJ_LOG_ERROR )
2522  {
2523  QgsDebugMsgLevel( QStringLiteral( "PROJ: %1" ).arg( message ), 2 );
2524  }
2525  else if ( level == PJ_LOG_DEBUG )
2526  {
2527  QgsDebugMsgLevel( QStringLiteral( "PROJ: %1" ).arg( message ), 3 );
2528  }
2529 }
2530 
2532 {
2533  setlocale( LC_ALL, "C" );
2534  QString dbFilePath = QgsApplication::srsDatabaseFilePath();
2535 
2536  int inserted = 0, updated = 0, deleted = 0, errors = 0;
2537 
2538  QgsDebugMsgLevel( QStringLiteral( "Load srs db from: %1" ).arg( QgsApplication::srsDatabaseFilePath().toLocal8Bit().constData() ), 4 );
2539 
2540  sqlite3_database_unique_ptr database;
2541  if ( database.open( dbFilePath ) != SQLITE_OK )
2542  {
2543  QgsDebugError( QStringLiteral( "Could not open database: %1 (%2)\n" ).arg( QgsApplication::srsDatabaseFilePath(), database.errorMessage() ) );
2544  return -1;
2545  }
2546 
2547  if ( sqlite3_exec( database.get(), "BEGIN TRANSACTION", nullptr, nullptr, nullptr ) != SQLITE_OK )
2548  {
2549  QgsDebugError( QStringLiteral( "Could not begin transaction: %1 (%2)\n" ).arg( QgsApplication::srsDatabaseFilePath(), database.errorMessage() ) );
2550  return -1;
2551  }
2552 
2553  sqlite3_statement_unique_ptr statement;
2554  int result;
2555  char *errMsg = nullptr;
2556 
2557  bool createdTypeColumn = false;
2558  if ( sqlite3_exec( database.get(), "ALTER TABLE tbl_srs ADD COLUMN srs_type text", nullptr, nullptr, nullptr ) == SQLITE_OK )
2559  {
2560  createdTypeColumn = true;
2561  if ( sqlite3_exec( database.get(), "CREATE INDEX srs_type ON tbl_srs(srs_type)", nullptr, nullptr, nullptr ) != SQLITE_OK )
2562  {
2563  QgsDebugError( QStringLiteral( "Could not create index for srs_type" ) );
2564  return -1;
2565  }
2566  }
2567 
2568  if ( sqlite3_exec( database.get(), "create table tbl_info (proj_major INT, proj_minor INT, proj_patch INT)", nullptr, nullptr, nullptr ) == SQLITE_OK )
2569  {
2570  QString sql = QStringLiteral( "INSERT INTO tbl_info(proj_major, proj_minor, proj_patch) VALUES (%1, %2,%3)" )
2571  .arg( QString::number( PROJ_VERSION_MAJOR ),
2572  QString::number( PROJ_VERSION_MINOR ),
2573  QString::number( PROJ_VERSION_PATCH ) );
2574  if ( sqlite3_exec( database.get(), sql.toUtf8(), nullptr, nullptr, &errMsg ) != SQLITE_OK )
2575  {
2576  QgsDebugError( QStringLiteral( "Could not execute: %1 [%2/%3]\n" ).arg(
2577  sql,
2578  database.errorMessage(),
2579  errMsg ? errMsg : "(unknown error)" ) );
2580  if ( errMsg )
2581  sqlite3_free( errMsg );
2582  return -1;
2583  }
2584  }
2585  else
2586  {
2587  // retrieve last update details
2588  QString sql = QStringLiteral( "SELECT proj_major, proj_minor, proj_patch FROM tbl_info" );
2589  statement = database.prepare( sql, result );
2590  if ( result != SQLITE_OK )
2591  {
2592  QgsDebugError( QStringLiteral( "Could not prepare: %1 [%2]\n" ).arg( sql, database.errorMessage() ) );
2593  return -1;
2594  }
2595  if ( statement.step() == SQLITE_ROW )
2596  {
2597  int major = statement.columnAsInt64( 0 );
2598  int minor = statement.columnAsInt64( 1 );
2599  int patch = statement.columnAsInt64( 2 );
2600  if ( !createdTypeColumn && major == PROJ_VERSION_MAJOR && minor == PROJ_VERSION_MINOR && patch == PROJ_VERSION_PATCH )
2601  // yay, nothing to do!
2602  return 0;
2603  }
2604  else
2605  {
2606  QgsDebugError( QStringLiteral( "Could not retrieve previous CRS sync PROJ version number" ) );
2607  return -1;
2608  }
2609  }
2610 
2611  PJ_CONTEXT *pjContext = QgsProjContext::get();
2612  // silence proj warnings
2613  proj_log_func( pjContext, nullptr, sync_db_proj_logger );
2614 
2615  PROJ_STRING_LIST authorities = proj_get_authorities_from_database( pjContext );
2616 
2617  int nextSrsId = 67218;
2618  int nextSrId = 520007218;
2619  for ( auto authIter = authorities; authIter && *authIter; ++authIter )
2620  {
2621  const QString authority( *authIter );
2622  QgsDebugMsgLevel( QStringLiteral( "Loading authority '%1'" ).arg( authority ), 2 );
2623  PROJ_STRING_LIST codes = proj_get_codes_from_database( pjContext, *authIter, PJ_TYPE_CRS, true );
2624 
2625  QStringList allCodes;
2626 
2627  for ( auto codesIter = codes; codesIter && *codesIter; ++codesIter )
2628  {
2629  const QString code( *codesIter );
2630  allCodes << QgsSqliteUtils::quotedString( code );
2631  QgsDebugMsgLevel( QStringLiteral( "Loading code '%1'" ).arg( code ), 4 );
2632  QgsProjUtils::proj_pj_unique_ptr crs( proj_create_from_database( pjContext, *authIter, *codesIter, PJ_CATEGORY_CRS, false, nullptr ) );
2633  if ( !crs )
2634  {
2635  QgsDebugError( QStringLiteral( "Could not load '%1:%2'" ).arg( authority, code ) );
2636  continue;
2637  }
2638 
2639  const PJ_TYPE pjType = proj_get_type( crs.get( ) );
2640 
2641  QString srsTypeString;
2642  // NOLINTBEGIN(bugprone-branch-clone)
2643  switch ( pjType )
2644  {
2645  // don't need these in the CRS db
2646  case PJ_TYPE_ELLIPSOID:
2647  case PJ_TYPE_PRIME_MERIDIAN:
2648  case PJ_TYPE_GEODETIC_REFERENCE_FRAME:
2649  case PJ_TYPE_DYNAMIC_GEODETIC_REFERENCE_FRAME:
2650  case PJ_TYPE_VERTICAL_REFERENCE_FRAME:
2651  case PJ_TYPE_DYNAMIC_VERTICAL_REFERENCE_FRAME:
2652  case PJ_TYPE_DATUM_ENSEMBLE:
2653  case PJ_TYPE_CONVERSION:
2654  case PJ_TYPE_TRANSFORMATION:
2655  case PJ_TYPE_CONCATENATED_OPERATION:
2656  case PJ_TYPE_OTHER_COORDINATE_OPERATION:
2657  case PJ_TYPE_TEMPORAL_DATUM:
2658  case PJ_TYPE_ENGINEERING_DATUM:
2659  case PJ_TYPE_PARAMETRIC_DATUM:
2660  case PJ_TYPE_UNKNOWN:
2661  continue;
2662 
2663  case PJ_TYPE_CRS:
2664  case PJ_TYPE_GEOGRAPHIC_CRS:
2665  continue; // not possible
2666 
2667  case PJ_TYPE_GEODETIC_CRS:
2668  srsTypeString = qgsEnumValueToKey( Qgis::CrsType::Geodetic );
2669  break;
2670 
2671  case PJ_TYPE_GEOCENTRIC_CRS:
2672  srsTypeString = qgsEnumValueToKey( Qgis::CrsType::Geocentric );
2673  break;
2674 
2675  case PJ_TYPE_GEOGRAPHIC_2D_CRS:
2677  break;
2678 
2679  case PJ_TYPE_GEOGRAPHIC_3D_CRS:
2681  break;
2682 
2683  case PJ_TYPE_PROJECTED_CRS:
2684  srsTypeString = qgsEnumValueToKey( Qgis::CrsType::Projected );
2685  break;
2686 
2687  case PJ_TYPE_COMPOUND_CRS:
2688  srsTypeString = qgsEnumValueToKey( Qgis::CrsType::Compound );
2689  break;
2690 
2691  case PJ_TYPE_TEMPORAL_CRS:
2692  srsTypeString = qgsEnumValueToKey( Qgis::CrsType::Temporal );
2693  break;
2694 
2695  case PJ_TYPE_ENGINEERING_CRS:
2696  srsTypeString = qgsEnumValueToKey( Qgis::CrsType::Engineering );
2697  break;
2698 
2699  case PJ_TYPE_BOUND_CRS:
2700  srsTypeString = qgsEnumValueToKey( Qgis::CrsType::Bound );
2701  break;
2702 
2703  case PJ_TYPE_VERTICAL_CRS:
2704  srsTypeString = qgsEnumValueToKey( Qgis::CrsType::Vertical );
2705  break;
2706 
2707 #if PROJ_VERSION_MAJOR>9 || (PROJ_VERSION_MAJOR==9 && PROJ_VERSION_MINOR>=2)
2708  case PJ_TYPE_DERIVED_PROJECTED_CRS:
2710  break;
2711  case PJ_TYPE_COORDINATE_METADATA:
2712  continue;
2713 #endif
2714  case PJ_TYPE_OTHER_CRS:
2715  srsTypeString = qgsEnumValueToKey( Qgis::CrsType::Other );
2716  break;
2717  }
2718  // NOLINTEND(bugprone-branch-clone)
2719 
2720  crs = QgsProjUtils::unboundCrs( crs.get() );
2721 
2722  QString proj4 = getFullProjString( crs.get() );
2723  proj4.replace( QLatin1String( "+type=crs" ), QString() );
2724  proj4 = proj4.trimmed();
2725 
2726  if ( proj4.isEmpty() )
2727  {
2728  QgsDebugMsgLevel( QStringLiteral( "No proj4 for '%1:%2'" ).arg( authority, code ), 2 );
2729  // satisfy not null constraint
2730  proj4 = "";
2731  }
2732 
2733  // there's a not-null constraint on these columns, so we must use empty strings instead
2734  QString operation = "";
2735  QString ellps = "";
2737 
2738  const QString translatedOperation = QgsCoordinateReferenceSystemUtils::translateProjection( operation );
2739  if ( translatedOperation.isEmpty() && !operation.isEmpty() )
2740  {
2741  std::cout << QStringLiteral( "Operation needs translation in QgsCoordinateReferenceSystemUtils::translateProjection: %1" ).arg( operation ).toLocal8Bit().constData() << std::endl;
2742  qFatal( "aborted" );
2743  }
2744 
2745  const bool deprecated = proj_is_deprecated( crs.get() );
2746  const QString name( proj_get_name( crs.get() ) );
2747 
2748  QString sql = QStringLiteral( "SELECT parameters,description,deprecated,srs_type FROM tbl_srs WHERE auth_name='%1' AND auth_id='%2'" ).arg( authority, code );
2749  statement = database.prepare( sql, result );
2750  if ( result != SQLITE_OK )
2751  {
2752  QgsDebugError( QStringLiteral( "Could not prepare: %1 [%2]\n" ).arg( sql, database.errorMessage() ) );
2753  continue;
2754  }
2755 
2756  QString dbSrsProj4;
2757  QString dbSrsDesc;
2758  QString dbSrsType;
2759  bool dbSrsDeprecated = deprecated;
2760  if ( statement.step() == SQLITE_ROW )
2761  {
2762  dbSrsProj4 = statement.columnAsText( 0 );
2763  dbSrsDesc = statement.columnAsText( 1 );
2764  dbSrsDeprecated = statement.columnAsText( 2 ).toInt() != 0;
2765  dbSrsType = statement.columnAsText( 3 );
2766  }
2767 
2768  if ( !dbSrsProj4.isEmpty() || !dbSrsDesc.isEmpty() )
2769  {
2770  if ( proj4 != dbSrsProj4 || name != dbSrsDesc || deprecated != dbSrsDeprecated || dbSrsType != srsTypeString )
2771  {
2772  errMsg = nullptr;
2773  sql = QStringLiteral( "UPDATE tbl_srs SET parameters=%1,description=%2,deprecated=%3, srs_type=%4 WHERE auth_name=%5 AND auth_id=%6" )
2774  .arg( QgsSqliteUtils::quotedString( proj4 ) )
2775  .arg( QgsSqliteUtils::quotedString( name ) )
2776  .arg( deprecated ? 1 : 0 )
2777  .arg( QgsSqliteUtils::quotedString( srsTypeString ),
2779 
2780  if ( sqlite3_exec( database.get(), sql.toUtf8(), nullptr, nullptr, &errMsg ) != SQLITE_OK )
2781  {
2782  QgsDebugError( QStringLiteral( "Could not execute: %1 [%2/%3]\n" ).arg(
2783  sql,
2784  database.errorMessage(),
2785  errMsg ? errMsg : "(unknown error)" ) );
2786  if ( errMsg )
2787  sqlite3_free( errMsg );
2788  errors++;
2789  }
2790  else
2791  {
2792  updated++;
2793  }
2794  }
2795  }
2796  else
2797  {
2798  const bool isGeographic = testIsGeographic( crs.get() );
2799 
2800  // work out srid and srsid
2801  const QString dbVals = sAuthIdToQgisSrsIdMap.value( QStringLiteral( "%1:%2" ).arg( authority, code ) );
2802  QString srsId;
2803  QString srId;
2804  if ( !dbVals.isEmpty() )
2805  {
2806  const QStringList parts = dbVals.split( ',' );
2807  srsId = parts.at( 0 );
2808  srId = parts.at( 1 );
2809  }
2810  if ( srId.isEmpty() )
2811  {
2812  srId = QString::number( nextSrId );
2813  nextSrId++;
2814  }
2815  if ( srsId.isEmpty() )
2816  {
2817  srsId = QString::number( nextSrsId );
2818  nextSrsId++;
2819  }
2820 
2821  if ( !srsId.isEmpty() )
2822  {
2823  sql = QStringLiteral( "INSERT INTO tbl_srs(srs_id, description,projection_acronym,ellipsoid_acronym,parameters,srid,auth_name,auth_id,is_geo,deprecated,srs_type) VALUES (%1, %2,%3,%4,%5,%6,%7,%8,%9,%10,%11)" )
2824  .arg( srsId )
2825  .arg( QgsSqliteUtils::quotedString( name ),
2828  QgsSqliteUtils::quotedString( proj4 ) )
2829  .arg( srId )
2830  .arg( QgsSqliteUtils::quotedString( authority ) )
2831  .arg( QgsSqliteUtils::quotedString( code ) )
2832  .arg( isGeographic ? 1 : 0 )
2833  .arg( deprecated ? 1 : 0 )
2834  .arg( QgsSqliteUtils::quotedString( srsTypeString ) );
2835  }
2836  else
2837  {
2838  sql = QStringLiteral( "INSERT INTO tbl_srs(description,projection_acronym,ellipsoid_acronym,parameters,srid,auth_name,auth_id,is_geo,deprecated,srs_type) VALUES (%1,%2,%3,%4,%5,%6,%7,%8,%9,%10)" )
2839  .arg( QgsSqliteUtils::quotedString( name ),
2842  QgsSqliteUtils::quotedString( proj4 ) )
2843  .arg( srId )
2844  .arg( QgsSqliteUtils::quotedString( authority ) )
2845  .arg( QgsSqliteUtils::quotedString( code ) )
2846  .arg( isGeographic ? 1 : 0 )
2847  .arg( deprecated ? 1 : 0 )
2848  .arg( QgsSqliteUtils::quotedString( srsTypeString ) );
2849  }
2850 
2851  errMsg = nullptr;
2852  if ( sqlite3_exec( database.get(), sql.toUtf8(), nullptr, nullptr, &errMsg ) == SQLITE_OK )
2853  {
2854  inserted++;
2855  }
2856  else
2857  {
2858  qCritical( "Could not execute: %s [%s/%s]\n",
2859  sql.toLocal8Bit().constData(),
2860  sqlite3_errmsg( database.get() ),
2861  errMsg ? errMsg : "(unknown error)" );
2862  errors++;
2863 
2864  if ( errMsg )
2865  sqlite3_free( errMsg );
2866  }
2867  }
2868  }
2869 
2870  proj_string_list_destroy( codes );
2871 
2872  const QString sql = QStringLiteral( "DELETE FROM tbl_srs WHERE auth_name='%1' AND NOT auth_id IN (%2)" ).arg( authority, allCodes.join( ',' ) );
2873  if ( sqlite3_exec( database.get(), sql.toUtf8(), nullptr, nullptr, nullptr ) == SQLITE_OK )
2874  {
2875  deleted = sqlite3_changes( database.get() );
2876  }
2877  else
2878  {
2879  errors++;
2880  qCritical( "Could not execute: %s [%s]\n",
2881  sql.toLocal8Bit().constData(),
2882  sqlite3_errmsg( database.get() ) );
2883  }
2884 
2885  }
2886  proj_string_list_destroy( authorities );
2887 
2888  QString sql = QStringLiteral( "UPDATE tbl_info set proj_major=%1,proj_minor=%2,proj_patch=%3" )
2889  .arg( QString::number( PROJ_VERSION_MAJOR ),
2890  QString::number( PROJ_VERSION_MINOR ),
2891  QString::number( PROJ_VERSION_PATCH ) );
2892  if ( sqlite3_exec( database.get(), sql.toUtf8(), nullptr, nullptr, &errMsg ) != SQLITE_OK )
2893  {
2894  QgsDebugError( QStringLiteral( "Could not execute: %1 [%2/%3]\n" ).arg(
2895  sql,
2896  database.errorMessage(),
2897  errMsg ? errMsg : "(unknown error)" ) );
2898  if ( errMsg )
2899  sqlite3_free( errMsg );
2900  return -1;
2901  }
2902 
2903  if ( sqlite3_exec( database.get(), "COMMIT", nullptr, nullptr, nullptr ) != SQLITE_OK )
2904  {
2905  QgsDebugError( QStringLiteral( "Could not commit transaction: %1 [%2]\n" ).arg(
2907  sqlite3_errmsg( database.get() ) )
2908  );
2909  return -1;
2910  }
2911 
2912 #ifdef QGISDEBUG
2913  QgsDebugMsgLevel( QStringLiteral( "CRS update (inserted:%1 updated:%2 deleted:%3 errors:%4)" ).arg( inserted ).arg( updated ).arg( deleted ).arg( errors ), 4 );
2914 #else
2915  Q_UNUSED( deleted )
2916 #endif
2917 
2918  if ( errors > 0 )
2919  return -errors;
2920  else
2921  return updated + inserted;
2922 }
2923 
2924 const QHash<QString, QgsCoordinateReferenceSystem> &QgsCoordinateReferenceSystem::stringCache()
2925 {
2926  return *sStringCache();
2927 }
2928 
2929 const QHash<QString, QgsCoordinateReferenceSystem> &QgsCoordinateReferenceSystem::projCache()
2930 {
2931  return *sProj4Cache();
2932 }
2933 
2934 const QHash<QString, QgsCoordinateReferenceSystem> &QgsCoordinateReferenceSystem::ogcCache()
2935 {
2936  return *sOgcCache();
2937 }
2938 
2939 const QHash<QString, QgsCoordinateReferenceSystem> &QgsCoordinateReferenceSystem::wktCache()
2940 {
2941  return *sWktCache();
2942 }
2943 
2944 const QHash<long, QgsCoordinateReferenceSystem> &QgsCoordinateReferenceSystem::srIdCache()
2945 {
2946  return *sSrIdCache();
2947 }
2948 
2949 const QHash<long, QgsCoordinateReferenceSystem> &QgsCoordinateReferenceSystem::srsIdCache()
2950 {
2951  return *sSrsIdCache();
2952 }
2953 
2955 {
2956  if ( isGeographic() )
2957  {
2958  return *this;
2959  }
2960 
2961  if ( PJ *obj = d->threadLocalProjObject() )
2962  {
2963  PJ_CONTEXT *pjContext = QgsProjContext::get();
2964  QgsProjUtils::proj_pj_unique_ptr geoCrs( proj_crs_get_geodetic_crs( pjContext, obj ) );
2965  if ( !geoCrs )
2967 
2968  if ( !testIsGeographic( geoCrs.get() ) )
2970 
2971  QgsProjUtils::proj_pj_unique_ptr normalized( proj_normalize_for_visualization( pjContext, geoCrs.get() ) );
2972  if ( !normalized )
2974 
2975  return QgsCoordinateReferenceSystem::fromProjObject( normalized.get() );
2976  }
2977  else
2978  {
2980  }
2981 }
2982 
2984 {
2985  switch ( type() )
2986  {
2994  case Qgis::CrsType::Bound:
2995  case Qgis::CrsType::Other:
2998  return *this;
2999 
3002 
3004  break;
3005  }
3006 
3007  if ( PJ *obj = d->threadLocalProjObject() )
3008  {
3010  if ( hozCrs )
3011  return QgsCoordinateReferenceSystem::fromProjObject( hozCrs.get() );
3012  }
3014 }
3015 
3017 {
3018  switch ( type() )
3019  {
3027  case Qgis::CrsType::Bound:
3028  case Qgis::CrsType::Other:
3032 
3034  return *this;
3035 
3037  break;
3038  }
3039 
3040  if ( PJ *obj = d->threadLocalProjObject() )
3041  {
3043  if ( vertCrs )
3044  return QgsCoordinateReferenceSystem::fromProjObject( vertCrs.get() );
3045  }
3047 }
3048 
3050 {
3051  if ( isGeographic() )
3052  {
3053  return d->mAuthId;
3054  }
3055  else if ( PJ *obj = d->threadLocalProjObject() )
3056  {
3057  QgsProjUtils::proj_pj_unique_ptr geoCrs( proj_crs_get_geodetic_crs( QgsProjContext::get(), obj ) );
3058  return geoCrs ? QStringLiteral( "%1:%2" ).arg( proj_get_id_auth_name( geoCrs.get(), 0 ), proj_get_id_code( geoCrs.get(), 0 ) ) : QString();
3059  }
3060  else
3061  {
3062  return QString();
3063  }
3064 }
3065 
3067 {
3068  return d->threadLocalProjObject();
3069 }
3070 
3072 {
3074  crs.createFromProjObject( object );
3075  return crs;
3076 }
3077 
3079 {
3080  d.detach();
3081  d->mIsValid = false;
3082  d->mProj4.clear();
3083  d->mWktPreferred.clear();
3084 
3085  if ( !object )
3086  {
3087  return false;
3088  }
3089 
3090  switch ( proj_get_type( object ) )
3091  {
3092  case PJ_TYPE_GEODETIC_CRS:
3093  case PJ_TYPE_GEOCENTRIC_CRS:
3094  case PJ_TYPE_GEOGRAPHIC_CRS:
3095  case PJ_TYPE_GEOGRAPHIC_2D_CRS:
3096  case PJ_TYPE_GEOGRAPHIC_3D_CRS:
3097  case PJ_TYPE_VERTICAL_CRS:
3098  case PJ_TYPE_PROJECTED_CRS:
3099  case PJ_TYPE_COMPOUND_CRS:
3100  case PJ_TYPE_TEMPORAL_CRS:
3101  case PJ_TYPE_ENGINEERING_CRS:
3102  case PJ_TYPE_BOUND_CRS:
3103  case PJ_TYPE_OTHER_CRS:
3104  break;
3105 
3106  default:
3107  return false;
3108  }
3109 
3110  d->setPj( QgsProjUtils::unboundCrs( object ) );
3111 
3112  if ( !d->hasPj() )
3113  {
3114  return d->mIsValid;
3115  }
3116  else
3117  {
3118  // maybe we can directly grab the auth name and code from the crs
3119  const QString authName( proj_get_id_auth_name( d->threadLocalProjObject(), 0 ) );
3120  const QString authCode( proj_get_id_code( d->threadLocalProjObject(), 0 ) );
3121  if ( !authName.isEmpty() && !authCode.isEmpty() && loadFromAuthCode( authName, authCode ) )
3122  {
3123  return d->mIsValid;
3124  }
3125  else
3126  {
3127  // Still a valid CRS, just not a known one
3128  d->mIsValid = true;
3129  d->mDescription = QString( proj_get_name( d->threadLocalProjObject() ) );
3130  setMapUnits();
3131  d->mIsGeographic = testIsGeographic( d->threadLocalProjObject() );
3132  }
3133  }
3134 
3135  return d->mIsValid;
3136 }
3137 
3139 {
3140  QStringList projections;
3141  const QList<QgsCoordinateReferenceSystem> res = QgsApplication::coordinateReferenceSystemRegistry()->recentCrs();
3142  projections.reserve( res.size() );
3143  for ( const QgsCoordinateReferenceSystem &crs : res )
3144  {
3145  projections << QString::number( crs.srsid() );
3146  }
3147  return projections;
3148 }
3149 
3151 {
3153 }
3154 
3156 {
3158 }
3159 
3161 {
3163 }
3164 
3166 {
3168 }
3169 
3171 {
3172  sSrIdCacheLock()->lockForWrite();
3173  if ( !sDisableSrIdCache )
3174  {
3175  if ( disableCache )
3176  sDisableSrIdCache = true;
3177  sSrIdCache()->clear();
3178  }
3179  sSrIdCacheLock()->unlock();
3180 
3181  sOgcLock()->lockForWrite();
3182  if ( !sDisableOgcCache )
3183  {
3184  if ( disableCache )
3185  sDisableOgcCache = true;
3186  sOgcCache()->clear();
3187  }
3188  sOgcLock()->unlock();
3189 
3190  sProj4CacheLock()->lockForWrite();
3191  if ( !sDisableProjCache )
3192  {
3193  if ( disableCache )
3194  sDisableProjCache = true;
3195  sProj4Cache()->clear();
3196  }
3197  sProj4CacheLock()->unlock();
3198 
3199  sCRSWktLock()->lockForWrite();
3200  if ( !sDisableWktCache )
3201  {
3202  if ( disableCache )
3203  sDisableWktCache = true;
3204  sWktCache()->clear();
3205  }
3206  sCRSWktLock()->unlock();
3207 
3208  sCRSSrsIdLock()->lockForWrite();
3209  if ( !sDisableSrsIdCache )
3210  {
3211  if ( disableCache )
3212  sDisableSrsIdCache = true;
3213  sSrsIdCache()->clear();
3214  }
3215  sCRSSrsIdLock()->unlock();
3216 
3217  sCrsStringLock()->lockForWrite();
3218  if ( !sDisableStringCache )
3219  {
3220  if ( disableCache )
3221  sDisableStringCache = true;
3222  sStringCache()->clear();
3223  }
3224  sCrsStringLock()->unlock();
3225 }
3226 
3227 // invalid < regular < user
3229 {
3230  if ( c1.d == c2.d )
3231  return false;
3232 
3233  if ( !c1.d->mIsValid && !c2.d->mIsValid )
3234  return false;
3235 
3236  if ( !c1.d->mIsValid && c2.d->mIsValid )
3237  return false;
3238 
3239  if ( c1.d->mIsValid && !c2.d->mIsValid )
3240  return true;
3241 
3242  const bool c1IsUser = c1.d->mSrsId >= USER_CRS_START_ID;
3243  const bool c2IsUser = c2.d->mSrsId >= USER_CRS_START_ID;
3244 
3245  if ( c1IsUser && !c2IsUser )
3246  return true;
3247 
3248  if ( !c1IsUser && c2IsUser )
3249  return false;
3250 
3251  if ( !c1IsUser && !c2IsUser && !c1.d->mAuthId.isEmpty() && !c2.d->mAuthId.isEmpty() )
3252  {
3253  if ( c1.d->mAuthId != c2.d->mAuthId )
3254  return c1.d->mAuthId > c2.d->mAuthId;
3255  }
3256 
3257  const QString wkt1 = c1.toWkt( Qgis::CrsWktVariant::Preferred );
3258  const QString wkt2 = c2.toWkt( Qgis::CrsWktVariant::Preferred );
3259  if ( wkt1 != wkt2 )
3260  return wkt1 > wkt2;
3261 
3262  if ( c1.d->mCoordinateEpoch == c2.d->mCoordinateEpoch )
3263  return false;
3264 
3265  if ( std::isnan( c1.d->mCoordinateEpoch ) && std::isnan( c2.d->mCoordinateEpoch ) )
3266  return false;
3267 
3268  if ( std::isnan( c1.d->mCoordinateEpoch ) && !std::isnan( c2.d->mCoordinateEpoch ) )
3269  return false;
3270 
3271  if ( !std::isnan( c1.d->mCoordinateEpoch ) && std::isnan( c2.d->mCoordinateEpoch ) )
3272  return true;
3273 
3274  return c1.d->mCoordinateEpoch > c2.d->mCoordinateEpoch;
3275 }
3276 
3278 {
3279  if ( c1.d == c2.d )
3280  return false;
3281 
3282  if ( !c1.d->mIsValid && !c2.d->mIsValid )
3283  return false;
3284 
3285  if ( c1.d->mIsValid && !c2.d->mIsValid )
3286  return false;
3287 
3288  if ( !c1.d->mIsValid && c2.d->mIsValid )
3289  return true;
3290 
3291  const bool c1IsUser = c1.d->mSrsId >= USER_CRS_START_ID;
3292  const bool c2IsUser = c2.d->mSrsId >= USER_CRS_START_ID;
3293 
3294  if ( !c1IsUser && c2IsUser )
3295  return true;
3296 
3297  if ( c1IsUser && !c2IsUser )
3298  return false;
3299 
3300  if ( !c1IsUser && !c2IsUser && !c1.d->mAuthId.isEmpty() && !c2.d->mAuthId.isEmpty() )
3301  {
3302  if ( c1.d->mAuthId != c2.d->mAuthId )
3303  return c1.d->mAuthId < c2.d->mAuthId;
3304  }
3305 
3306  const QString wkt1 = c1.toWkt( Qgis::CrsWktVariant::Preferred );
3307  const QString wkt2 = c2.toWkt( Qgis::CrsWktVariant::Preferred );
3308  if ( wkt1 != wkt2 )
3309  return wkt1 < wkt2;
3310 
3311  if ( c1.d->mCoordinateEpoch == c2.d->mCoordinateEpoch )
3312  return false;
3313 
3314  if ( std::isnan( c1.d->mCoordinateEpoch ) && std::isnan( c2.d->mCoordinateEpoch ) )
3315  return false;
3316 
3317  if ( !std::isnan( c1.d->mCoordinateEpoch ) && std::isnan( c2.d->mCoordinateEpoch ) )
3318  return false;
3319 
3320  if ( std::isnan( c1.d->mCoordinateEpoch ) && !std::isnan( c2.d->mCoordinateEpoch ) )
3321  return true;
3322 
3323  return c1.d->mCoordinateEpoch < c2.d->mCoordinateEpoch;
3324 }
3325 
3327 {
3328  return !( c1 < c2 );
3329 }
3331 {
3332  return !( c1 > c2 );
3333 }
CrsIdentifierType
Available identifier string types for representing coordinate reference systems.
Definition: qgis.h:1936
@ ShortString
A heavily abbreviated string, for use when a compact representation is required.
@ MediumString
A medium-length string, recommended for general purpose use.
DistanceUnit
Units of distance.
Definition: qgis.h:4090
@ Feet
Imperial feet.
@ Centimeters
Centimeters.
@ Millimeters
Millimeters.
@ Miles
Terrestrial miles.
@ Unknown
Unknown distance unit.
@ Yards
Imperial yards.
@ Degrees
Degrees, for planar geographic CRS distance measurements.
@ NauticalMiles
Nautical miles.
@ Kilometers
Kilometers.
CrsType
Coordinate reference system types.
Definition: qgis.h:1846
@ Vertical
Vertical CRS.
@ Temporal
Temporal CRS.
@ Compound
Compound (horizontal + vertical) CRS.
@ Projected
Projected CRS.
@ Other
Other type.
@ Bound
Bound CRS.
@ DerivedProjected
Derived projected CRS.
@ Unknown
Unknown type.
@ Engineering
Engineering CRS.
@ Geographic3d
3D geopraphic CRS
@ Geodetic
Geodetic CRS.
@ Geographic2d
2D geographic CRS
@ Geocentric
Geocentric CRS.
CrsDefinitionFormat
CRS definition formats.
Definition: qgis.h:3147
@ Wkt
WKT format (always recommended over proj string format)
CrsAxisDirection
Coordinate reference system axis directions.
Definition: qgis.h:1871
@ ColumnPositive
Column positive.
@ SouthSouthEast
South South East.
@ NorthWest
North West.
@ ColumnNegative
Column negative.
@ RowPositive
Row positive.
@ DisplayDown
Display down.
@ GeocentricZ
Geocentric (Z)
@ DisplayRight
Display right.
@ WestSouthWest
West South West.
@ RowNegative
Row negative.
@ NorthNorthEast
North North East.
@ EastNorthEast
East North East.
@ Unspecified
Unspecified.
@ NorthEast
North East.
@ NorthNorthWest
North North West.
@ GeocentricY
Geocentric (Y)
@ SouthEast
South East.
@ CounterClockwise
Counter clockwise.
@ SouthSouthWest
South South West.
@ DisplayLeft
Display left.
@ WestNorthWest
West North West.
@ EastSouthEast
East South East.
@ SouthWest
South West.
@ DisplayUp
Display up.
@ GeocentricX
Geocentric (X)
CrsWktVariant
Coordinate reference system WKT formatting variants.
Definition: qgis.h:1951
@ Wkt2_2019Simplified
WKT2_2019 with the simplification rule of WKT2_SIMPLIFIED.
@ Wkt2_2015Simplified
Same as WKT2_2015 with the following exceptions: UNIT keyword used. ID node only on top element....
@ Wkt1Esri
WKT1 as traditionally output by ESRI software, deriving from OGC 99-049.
@ Preferred
Preferred format, matching the most recent WKT ISO standard. Currently an alias to WKT2_2019,...
@ Wkt2_2019
Full WKT2 string, conforming to ISO 19162:2019 / OGC 18-010, with all possible nodes and new keyword ...
@ Wkt2_2015
Full WKT2 string, conforming to ISO 19162:2015(E) / OGC 12-063r5 with all possible nodes and new keyw...
@ Wkt1Gdal
WKT1 as traditionally output by GDAL, deriving from OGC 01-009. A notable departure from WKT1_GDAL wi...
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.
void removeRecent(const QgsCoordinateReferenceSystem &crs)
Removes a CRS from the list of recently used CRS.
long addUserCrs(const QgsCoordinateReferenceSystem &crs, const QString &name, Qgis::CrsDefinitionFormat nativeFormat=Qgis::CrsDefinitionFormat::Wkt)
Adds a new crs definition as a custom ("USER") CRS.
void clearRecent()
Cleans the list of recently used CRS.
QList< QgsCoordinateReferenceSystem > recentCrs()
Returns a list of recently used CRS.
void pushRecent(const QgsCoordinateReferenceSystem &crs)
Pushes a recently used CRS to the top of the recent CRS list.
QMap< QString, QgsProjOperation > projOperations() const
Returns a map of all valid PROJ operations.
static QString translateProjection(const QString &projection)
Returns a translated string for a projection method.
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.
Q_GADGET Qgis::DistanceUnit mapUnits
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.
QString toOgcUri() const
Returns the crs as OGC URI (format: http://www.opengis.net/def/crs/OGC/1.3/CRS84) Returns an empty st...
static void setCustomCrsValidation(CUSTOM_CRS_VALIDATION f)
Sets custom function to force valid CRS.
static Q_DECL_DEPRECATED void pushRecentCoordinateReferenceSystem(const QgsCoordinateReferenceSystem &crs)
Pushes a recently used CRS to the top of the recent CRS list.
long postgisSrid() const
Returns PostGIS SRID for the CRS.
QgsCoordinateReferenceSystem horizontalCrs() const
Returns the horizontal CRS associated with this CRS object.
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.
QString userFriendlyIdentifier(Qgis::CrsIdentifierType type=Qgis::CrsIdentifierType::MediumString) const
Returns a user friendly identifier for the CRS.
Qgis::CrsDefinitionFormat nativeFormat() const
Returns the native format for the CRS definition.
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.
void setNativeFormat(Qgis::CrsDefinitionFormat format)
Sets the native format for the CRS definition.
bool createFromProj(const QString &projString, bool identify=true)
Sets this CRS by passing it a PROJ style formatted string.
QgsCoordinateReferenceSystem verticalCrs() const
Returns the vertical CRS associated with this CRS object.
static Q_DECL_DEPRECATED void removeRecentCoordinateReferenceSystem(const QgsCoordinateReferenceSystem &crs)
Removes a CRS from the list of recently used CRS.
QgsDatumEnsemble datumEnsemble() const
Attempts to retrieve datum ensemble details from the CRS.
QgsCoordinateReferenceSystem toGeographicCrs() const
Returns the geographic CRS associated with this CRS object.
bool isDynamic() const
Returns true if the CRS is a dynamic CRS.
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 QgsCoordinateReferenceSystem fromProjObject(PJ *object)
Constructs a QgsCoordinateReferenceSystem from a PROJ PJ object.
static void invalidateCache(bool disableCache=false)
Clears the internal cache used to initialize QgsCoordinateReferenceSystem objects.
static QgsCoordinateReferenceSystem createCompoundCrs(const QgsCoordinateReferenceSystem &horizontalCrs, const QgsCoordinateReferenceSystem &verticalCrs)
Given a horizontal and vertical CRS, attempts to create a compound CRS from them.
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 Q_DECL_DEPRECATED QList< QgsCoordinateReferenceSystem > recentCoordinateReferenceSystems()
Returns a list of recently used CRS.
QString toWkt(Qgis::CrsWktVariant variant=Qgis::CrsWktVariant::Wkt1Gdal, bool multiline=false, int indentationWidth=4) const
Returns a WKT representation of this CRS.
PJ * projObject() const
Returns the underlying PROJ PJ object corresponding to the CRS, or nullptr if the CRS is invalid.
void setCoordinateEpoch(double epoch)
Sets the coordinate epoch, as a decimal year.
Q_DECL_DEPRECATED bool createFromSrid(long srid)
Sets this CRS by lookup of the given PostGIS SRID in the CRS database.
long saveAsUserCrs(const QString &name, Qgis::CrsDefinitionFormat nativeFormat=Qgis::CrsDefinitionFormat::Wkt)
Saves the CRS as a new custom ("USER") CRS.
static Q_DECL_DEPRECATED void clearRecentCoordinateReferenceSystems()
Cleans the list of recently used CRS.
QString celestialBodyName() const
Attempts to retrieve the name of the celestial body associated with the CRS (e.g.
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.
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 validationHint() const
Gets user hint for validation.
QgsProjOperation operation() const
Returns information about the PROJ operation associated with the coordinate reference system,...
QList< Qgis::CrsAxisDirection > axisOrdering() const
Returns an ordered list of the axis directions reflecting the native axis order for the CRS.
long srsid() const
Returns the internal CRS ID, if available.
Qgis::CrsType type() const
Returns the type of the CRS.
bool hasAxisInverted() const
Returns whether the axis order is inverted for the CRS compared to the order east/north (longitude/la...
bool createFromProjObject(PJ *object)
Sets this CRS by passing it a PROJ PJ object, corresponding to a PROJ CRS object.
bool isDeprecated() const
Returns true if the CRS is considered deprecated.
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:131
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
CRSFlavor
CRS flavor.
Definition: qgsogcutils.h:605
@ AUTH_CODE
unknown/unhandled flavor
static CRSFlavor parseCrsName(const QString &crsName, QString &authority, QString &code)
Parse a CRS name in one of the flavors of OGC services, and decompose it as authority and code.
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.
static proj_pj_unique_ptr crsToHorizontalCrs(const PJ *crs)
Given a PROJ crs (which may be a compound or bound crs, or some other type), extract the horizontal c...
@ 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 proj_pj_unique_ptr unboundCrs(const PJ *crs)
Given a PROJ crs (which may be a compound or bound crs, or some other type), ensure that it is not a ...
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 createCompoundCrs(const PJ *horizontalCrs, const PJ *verticalCrs)
Given a PROJ horizontal and vertical CRS, attempt to create a compound CRS from them.
static proj_pj_unique_ptr crsToVerticalCrs(const PJ *crs)
Given a PROJ crs (which may be a compound crs, or some other type), extract the vertical crs from it.
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)
Set the minimum y value.
Definition: qgsrectangle.h:159
void setXMinimum(double x)
Set the minimum x value.
Definition: qgsrectangle.h:149
void setYMaximum(double y)
Set the maximum y value.
Definition: qgsrectangle.h:164
void setXMaximum(double x)
Set the maximum x value.
Definition: qgsrectangle.h:154
static QString quotedString(const QString &value)
Returns a quoted string value, surround by ' characters and with special characters correctly escaped...
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.
#define Q_NOWARN_DEPRECATED_POP
Definition: qgis.h:5741
QString qgsDoubleToString(double a, int precision=17)
Returns a string representation of a double.
Definition: qgis.h:5089
QString qgsEnumValueToKey(const T &value, bool *returnOk=nullptr)
Returns the value for the given key of an enum.
Definition: qgis.h:5363
#define Q_NOWARN_DEPRECATED_PUSH
Definition: qgis.h:5740
bool qgsNanCompatibleEquals(double a, double b)
Compare two doubles, treating nan values as equal.
Definition: qgis.h:5134
const int USER_CRS_START_ID
Magick number that determines whether a projection crsid is a system (srs.db) or user (~/....
Definition: qgis.h:5689
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 QgsDebugError(str)
Definition: qgslogger.h:38
const QgsCoordinateReferenceSystem & crs