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