QGIS API Documentation  3.24.2-Tisler (13c1a02865)
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 
797 {
798  return createFromWktInternal( wkt, QString() );
799 }
800 
801 bool QgsCoordinateReferenceSystem::createFromWktInternal( const QString &wkt, const QString &description )
802 {
803  if ( wkt.isEmpty() )
804  return false;
805 
806  d.detach();
807 
808  QgsReadWriteLocker locker( *sCRSWktLock(), QgsReadWriteLocker::Read );
809  if ( !sDisableWktCache )
810  {
811  QHash< QString, QgsCoordinateReferenceSystem >::const_iterator crsIt = sWktCache()->constFind( wkt );
812  if ( crsIt != sWktCache()->constEnd() )
813  {
814  // found a match in the cache
815  *this = crsIt.value();
816 
817  if ( !description.isEmpty() && d->mDescription.isEmpty() )
818  {
819  // now we have a name for a previously unknown CRS! Update the cached CRS accordingly, so that we use the name from now on...
820  d->mDescription = description;
821  locker.changeMode( QgsReadWriteLocker::Write );
822  sWktCache()->insert( wkt, *this );
823  }
824  return d->mIsValid;
825  }
826  }
827  locker.unlock();
828 
829  d->mIsValid = false;
830  d->mProj4.clear();
831  d->mWktPreferred.clear();
832  if ( wkt.isEmpty() )
833  {
834  QgsDebugMsgLevel( QStringLiteral( "theWkt is uninitialized, operation failed" ), 4 );
835  return d->mIsValid;
836  }
837 
838  // try to match against user crs
839  QgsCoordinateReferenceSystem::RecordMap record = getRecord( "select * from tbl_srs where wkt=" + QgsSqliteUtils::quotedString( wkt ) + " order by deprecated" );
840  if ( !record.empty() )
841  {
842  long srsId = record[QStringLiteral( "srs_id" )].toLong();
843  if ( srsId > 0 )
844  {
845  createFromSrsId( srsId );
846  }
847  }
848  else
849  {
850  setWktString( wkt );
851  if ( !description.isEmpty() )
852  {
853  d->mDescription = description;
854  }
855  if ( d->mSrsId == 0 )
856  {
857  // lastly, try a tolerant match of the created proj object against all user CRSes (allowing differences in parameter order during the comparison)
858  long id = matchToUserCrs();
859  if ( id >= USER_CRS_START_ID )
860  {
861  createFromSrsId( id );
862  }
863  }
864  }
865 
866  locker.changeMode( QgsReadWriteLocker::Write );
867  if ( !sDisableWktCache )
868  sWktCache()->insert( wkt, *this );
869 
870  return d->mIsValid;
871  //setMapunits will be called by createfromproj above
872 }
873 
875 {
876  return d->mIsValid;
877 }
878 
879 bool QgsCoordinateReferenceSystem::createFromProj4( const QString &proj4String )
880 {
881  return createFromProj( proj4String );
882 }
883 
884 bool QgsCoordinateReferenceSystem::createFromProj( const QString &projString, const bool identify )
885 {
886  if ( projString.isEmpty() )
887  return false;
888 
889  d.detach();
890 
891  if ( projString.trimmed().isEmpty() )
892  {
893  d->mIsValid = false;
894  d->mProj4.clear();
895  d->mWktPreferred.clear();
896  return false;
897  }
898 
899  QgsReadWriteLocker locker( *sProj4CacheLock(), QgsReadWriteLocker::Read );
900  if ( !sDisableProjCache )
901  {
902  QHash< QString, QgsCoordinateReferenceSystem >::const_iterator crsIt = sProj4Cache()->constFind( projString );
903  if ( crsIt != sProj4Cache()->constEnd() )
904  {
905  // found a match in the cache
906  *this = crsIt.value();
907  return d->mIsValid;
908  }
909  }
910  locker.unlock();
911 
912  //
913  // Examples:
914  // +proj=tmerc +lat_0=0 +lon_0=-62 +k=0.999500 +x_0=400000 +y_0=0
915  // +ellps=clrk80 +towgs84=-255,-15,71,0,0,0,0 +units=m +no_defs
916  //
917  // +proj=lcc +lat_1=46.8 +lat_0=46.8 +lon_0=2.337229166666664 +k_0=0.99987742
918  // +x_0=600000 +y_0=2200000 +a=6378249.2 +b=6356515.000000472 +units=m +no_defs
919  //
920  QString myProj4String = projString.trimmed();
921  myProj4String.remove( QStringLiteral( "+type=crs" ) );
922  myProj4String = myProj4String.trimmed();
923 
924  d->mIsValid = false;
925  d->mWktPreferred.clear();
926 
927  if ( identify )
928  {
929  // first, try to use proj to do this for us...
930  const QString projCrsString = myProj4String + ( myProj4String.contains( QStringLiteral( "+type=crs" ) ) ? QString() : QStringLiteral( " +type=crs" ) );
931  QgsProjUtils::proj_pj_unique_ptr crs( proj_create( QgsProjContext::get(), projCrsString.toLatin1().constData() ) );
932  if ( crs )
933  {
934  QString authName;
935  QString authCode;
937  {
938  const QString authid = QStringLiteral( "%1:%2" ).arg( authName, authCode );
939  if ( createFromOgcWmsCrs( authid ) )
940  {
942  if ( !sDisableProjCache )
943  sProj4Cache()->insert( projString, *this );
944  return d->mIsValid;
945  }
946  }
947  }
948 
949  // try a direct match against user crses
950  QgsCoordinateReferenceSystem::RecordMap myRecord = getRecord( "select * from tbl_srs where parameters=" + QgsSqliteUtils::quotedString( myProj4String ) + " order by deprecated" );
951  long id = 0;
952  if ( !myRecord.empty() )
953  {
954  id = myRecord[QStringLiteral( "srs_id" )].toLong();
955  if ( id >= USER_CRS_START_ID )
956  {
957  createFromSrsId( id );
958  }
959  }
960  if ( id < USER_CRS_START_ID )
961  {
962  // no direct matches, so go ahead and create a new proj object based on the proj string alone.
963  setProjString( myProj4String );
964 
965  // lastly, try a tolerant match of the created proj object against all user CRSes (allowing differences in parameter order during the comparison)
966  id = matchToUserCrs();
967  if ( id >= USER_CRS_START_ID )
968  {
969  createFromSrsId( id );
970  }
971  }
972  }
973  else
974  {
975  setProjString( myProj4String );
976  }
977 
979  if ( !sDisableProjCache )
980  sProj4Cache()->insert( projString, *this );
981 
982  return d->mIsValid;
983 }
984 
985 //private method meant for internal use by this class only
986 QgsCoordinateReferenceSystem::RecordMap QgsCoordinateReferenceSystem::getRecord( const QString &sql )
987 {
988  QString myDatabaseFileName;
989  QgsCoordinateReferenceSystem::RecordMap myMap;
990  QString myFieldName;
991  QString myFieldValue;
994  int myResult;
995 
996  // Get the full path name to the sqlite3 spatial reference database.
997  myDatabaseFileName = QgsApplication::srsDatabaseFilePath();
998  QFileInfo myInfo( myDatabaseFileName );
999  if ( !myInfo.exists() )
1000  {
1001  QgsDebugMsg( "failed : " + myDatabaseFileName + " does not exist!" );
1002  return myMap;
1003  }
1004 
1005  //check the db is available
1006  myResult = openDatabase( myDatabaseFileName, database );
1007  if ( myResult != SQLITE_OK )
1008  {
1009  return myMap;
1010  }
1011 
1012  statement = database.prepare( sql, myResult );
1013  // XXX Need to free memory from the error msg if one is set
1014  if ( myResult == SQLITE_OK && statement.step() == SQLITE_ROW )
1015  {
1016  int myColumnCount = statement.columnCount();
1017  //loop through each column in the record adding its expression name and value to the map
1018  for ( int myColNo = 0; myColNo < myColumnCount; myColNo++ )
1019  {
1020  myFieldName = statement.columnName( myColNo );
1021  myFieldValue = statement.columnAsText( myColNo );
1022  myMap[myFieldName] = myFieldValue;
1023  }
1024  if ( statement.step() != SQLITE_DONE )
1025  {
1026  QgsDebugMsgLevel( QStringLiteral( "Multiple records found in srs.db" ), 4 );
1027  //be less fussy on proj 6 -- the db has MANY more entries!
1028  }
1029  }
1030  else
1031  {
1032  QgsDebugMsgLevel( "failed : " + sql, 4 );
1033  }
1034 
1035  if ( myMap.empty() )
1036  {
1037  myDatabaseFileName = QgsApplication::qgisUserDatabaseFilePath();
1038  QFileInfo myFileInfo;
1039  myFileInfo.setFile( myDatabaseFileName );
1040  if ( !myFileInfo.exists() )
1041  {
1042  QgsDebugMsg( QStringLiteral( "user qgis.db not found" ) );
1043  return myMap;
1044  }
1045 
1046  //check the db is available
1047  myResult = openDatabase( myDatabaseFileName, database );
1048  if ( myResult != SQLITE_OK )
1049  {
1050  return myMap;
1051  }
1052 
1053  statement = database.prepare( sql, myResult );
1054  // XXX Need to free memory from the error msg if one is set
1055  if ( myResult == SQLITE_OK && statement.step() == SQLITE_ROW )
1056  {
1057  int myColumnCount = statement.columnCount();
1058  //loop through each column in the record adding its field name and value to the map
1059  for ( int myColNo = 0; myColNo < myColumnCount; myColNo++ )
1060  {
1061  myFieldName = statement.columnName( myColNo );
1062  myFieldValue = statement.columnAsText( myColNo );
1063  myMap[myFieldName] = myFieldValue;
1064  }
1065 
1066  if ( statement.step() != SQLITE_DONE )
1067  {
1068  QgsDebugMsgLevel( QStringLiteral( "Multiple records found in srs.db" ), 4 );
1069  myMap.clear();
1070  }
1071  }
1072  else
1073  {
1074  QgsDebugMsgLevel( "failed : " + sql, 4 );
1075  }
1076  }
1077  return myMap;
1078 }
1079 
1080 // Accessors -----------------------------------
1081 
1083 {
1084  return d->mSrsId;
1085 }
1086 
1088 {
1089  return d->mSRID;
1090 }
1091 
1093 {
1094  return d->mAuthId;
1095 }
1096 
1098 {
1099  if ( d->mDescription.isNull() )
1100  {
1101  return QString();
1102  }
1103  else
1104  {
1105  return d->mDescription;
1106  }
1107 }
1108 
1110 {
1111  QString id;
1112  if ( !authid().isEmpty() )
1113  {
1114  if ( type != ShortString && !description().isEmpty() )
1115  id = QStringLiteral( "%1 - %2" ).arg( authid(), description() );
1116  else
1117  id = authid();
1118  }
1119  else if ( !description().isEmpty() )
1120  id = description();
1121  else if ( type == ShortString )
1122  id = isValid() ? QObject::tr( "Custom CRS" ) : QObject::tr( "Unknown CRS" );
1123  else if ( !toWkt( WKT_PREFERRED ).isEmpty() )
1124  id = QObject::tr( "Custom CRS: %1" ).arg(
1125  type == MediumString ? ( toWkt( WKT_PREFERRED ).left( 50 ) + QString( QChar( 0x2026 ) ) )
1126  : toWkt( WKT_PREFERRED ) );
1127  else if ( !toProj().isEmpty() )
1128  id = QObject::tr( "Custom CRS: %1" ).arg( type == MediumString ? ( toProj().left( 50 ) + QString( QChar( 0x2026 ) ) )
1129  : toProj() );
1130  if ( !id.isEmpty() && !std::isnan( d->mCoordinateEpoch ) )
1131  id += QStringLiteral( " @ %1" ).arg( d->mCoordinateEpoch );
1132 
1133  return id;
1134 }
1135 
1137 {
1138  if ( d->mProjectionAcronym.isNull() )
1139  {
1140  return QString();
1141  }
1142  else
1143  {
1144  return d->mProjectionAcronym;
1145  }
1146 }
1147 
1150  if ( d->mEllipsoidAcronym.isNull() )
1151  {
1152  if ( PJ *obj = d->threadLocalProjObject() )
1153  {
1154  QgsProjUtils::proj_pj_unique_ptr ellipsoid( proj_get_ellipsoid( QgsProjContext::get(), obj ) );
1155  if ( ellipsoid )
1156  {
1157  const QString ellipsoidAuthName( proj_get_id_auth_name( ellipsoid.get(), 0 ) );
1158  const QString ellipsoidAuthCode( proj_get_id_code( ellipsoid.get(), 0 ) );
1159  if ( !ellipsoidAuthName.isEmpty() && !ellipsoidAuthCode.isEmpty() )
1160  d->mEllipsoidAcronym = QStringLiteral( "%1:%2" ).arg( ellipsoidAuthName, ellipsoidAuthCode );
1161  else
1162  {
1163  double semiMajor, semiMinor, invFlattening;
1164  int semiMinorComputed = 0;
1165  if ( proj_ellipsoid_get_parameters( QgsProjContext::get(), ellipsoid.get(), &semiMajor, &semiMinor, &semiMinorComputed, &invFlattening ) )
1166  {
1167  d->mEllipsoidAcronym = QStringLiteral( "PARAMETER:%1:%2" ).arg( qgsDoubleToString( semiMajor ),
1168  qgsDoubleToString( semiMinor ) );
1169  }
1170  else
1171  {
1172  d->mEllipsoidAcronym.clear();
1173  }
1174  }
1175  }
1176  }
1177  return d->mEllipsoidAcronym;
1178  }
1179  else
1180  {
1181  return d->mEllipsoidAcronym;
1182  }
1183 }
1184 
1186 {
1187  return toProj();
1188 }
1189 
1191 {
1192  if ( !d->mIsValid )
1193  return QString();
1194 
1195  if ( d->mProj4.isEmpty() )
1196  {
1197  if ( PJ *obj = d->threadLocalProjObject() )
1198  {
1199  d->mProj4 = getFullProjString( obj );
1200  }
1201  }
1202  // Stray spaces at the end?
1203  return d->mProj4.trimmed();
1204 }
1205 
1207 {
1208  return d->mIsGeographic;
1209 }
1210 
1212 {
1213  const PJ *pj = projObject();
1214  if ( !pj )
1215  return false;
1216 
1217  return QgsProjUtils::isDynamic( pj );
1218 }
1219 
1221 {
1222  const PJ *pj = projObject();
1223  if ( !pj )
1224  return QString();
1225 
1226 #if PROJ_VERSION_MAJOR>8 || (PROJ_VERSION_MAJOR==8 && PROJ_VERSION_MINOR>=1)
1227  PJ_CONTEXT *context = QgsProjContext::get();
1228 
1229  return QString( proj_get_celestial_body_name( context, pj ) );
1230 #else
1231  throw QgsNotSupportedException( QStringLiteral( "Retrieving celestial body requires a QGIS build based on PROJ 8.1 or later" ) );
1232 #endif
1233 }
1234 
1236 {
1237  if ( d->mCoordinateEpoch == epoch )
1238  return;
1239 
1240  // detaching clears the proj object, so we need to clone the existing one first
1242  d.detach();
1243  d->mCoordinateEpoch = epoch;
1244  d->setPj( std::move( clone ) );
1245 }
1246 
1248 {
1249  return d->mCoordinateEpoch;
1250 }
1251 
1253 {
1254  QgsDatumEnsemble res;
1255  res.mValid = false;
1256 
1257  const PJ *pj = projObject();
1258  if ( !pj )
1259  return res;
1260 
1261 #if PROJ_VERSION_MAJOR>=8
1262  PJ_CONTEXT *context = QgsProjContext::get();
1263 
1265  if ( !ensemble )
1266  return res;
1267 
1268  res.mValid = true;
1269  res.mName = QString( proj_get_name( ensemble.get() ) );
1270  res.mAuthority = QString( proj_get_id_auth_name( ensemble.get(), 0 ) );
1271  res.mCode = QString( proj_get_id_code( ensemble.get(), 0 ) );
1272  res.mRemarks = QString( proj_get_remarks( ensemble.get() ) );
1273  res.mScope = QString( proj_get_scope( ensemble.get() ) );
1274  res.mAccuracy = proj_datum_ensemble_get_accuracy( context, ensemble.get() );
1275 
1276  const int memberCount = proj_datum_ensemble_get_member_count( context, ensemble.get() );
1277  for ( int i = 0; i < memberCount; ++i )
1278  {
1279  QgsProjUtils::proj_pj_unique_ptr member( proj_datum_ensemble_get_member( context, ensemble.get(), i ) );
1280  if ( !member )
1281  continue;
1282 
1283  QgsDatumEnsembleMember details;
1284  details.mName = QString( proj_get_name( member.get() ) );
1285  details.mAuthority = QString( proj_get_id_auth_name( member.get(), 0 ) );
1286  details.mCode = QString( proj_get_id_code( member.get(), 0 ) );
1287  details.mRemarks = QString( proj_get_remarks( member.get() ) );
1288  details.mScope = QString( proj_get_scope( member.get() ) );
1289 
1290  res.mMembers << details;
1291  }
1292  return res;
1293 #else
1294  throw QgsNotSupportedException( QStringLiteral( "Calculating datum ensembles requires a QGIS build based on PROJ 8.0 or later" ) );
1295 #endif
1296 }
1297 
1299 {
1301 
1302  // we have to make a transformation object corresponding to the crs
1303  QString projString = toProj();
1304  projString.replace( QLatin1String( "+type=crs" ), QString() );
1305 
1306  QgsProjUtils::proj_pj_unique_ptr transformation( proj_create( QgsProjContext::get(), projString.toUtf8().constData() ) );
1307  if ( !transformation )
1308  return res;
1309 
1310  PJ_COORD coord = proj_coord( 0, 0, 0, HUGE_VAL );
1311  coord.uv.u = point.x() * M_PI / 180.0;
1312  coord.uv.v = point.y() * M_PI / 180.0;
1313 
1314  proj_errno_reset( transformation.get() );
1315  const PJ_FACTORS pjFactors = proj_factors( transformation.get(), coord );
1316  if ( proj_errno( transformation.get() ) )
1317  {
1318  return res;
1319  }
1320 
1321  res.mIsValid = true;
1322  res.mMeridionalScale = pjFactors.meridional_scale;
1323  res.mParallelScale = pjFactors.parallel_scale;
1324  res.mArealScale = pjFactors.areal_scale;
1325  res.mAngularDistortion = pjFactors.angular_distortion;
1326  res.mMeridianParallelAngle = pjFactors.meridian_parallel_angle * 180 / M_PI;
1327  res.mMeridianConvergence = pjFactors.meridian_convergence * 180 / M_PI;
1328  res.mTissotSemimajor = pjFactors.tissot_semimajor;
1329  res.mTissotSemiminor = pjFactors.tissot_semiminor;
1330  res.mDxDlam = pjFactors.dx_dlam;
1331  res.mDxDphi = pjFactors.dx_dphi;
1332  res.mDyDlam = pjFactors.dy_dlam;
1333  res.mDyDphi = pjFactors.dy_dphi;
1334  return res;
1335 }
1336 
1338 {
1339  QgsProjOperation res;
1340 
1341  // we have to make a transformation object corresponding to the crs
1342  QString projString = toProj();
1343  projString.replace( QLatin1String( "+type=crs" ), QString() );
1344 
1345  QgsProjUtils::proj_pj_unique_ptr transformation( proj_create( QgsProjContext::get(), projString.toUtf8().constData() ) );
1346  if ( !transformation )
1347  return res;
1348 
1349  PJ_PROJ_INFO info = proj_pj_info( transformation.get() );
1350 
1351  if ( info.id )
1352  {
1353  return QgsApplication::coordinateReferenceSystemRegistry()->projOperations().value( QString( info.id ) );
1354  }
1355 
1356  return res;
1357 }
1358 
1360 {
1361  if ( !d->mIsValid )
1363 
1364  return d->mMapUnits;
1365 }
1366 
1368 {
1369  if ( !d->mIsValid )
1370  return QgsRectangle();
1371 
1372  PJ *obj = d->threadLocalProjObject();
1373  if ( !obj )
1374  return QgsRectangle();
1375 
1376  double westLon = 0;
1377  double southLat = 0;
1378  double eastLon = 0;
1379  double northLat = 0;
1380 
1381  if ( !proj_get_area_of_use( QgsProjContext::get(), obj,
1382  &westLon, &southLat, &eastLon, &northLat, nullptr ) )
1383  return QgsRectangle();
1384 
1385 
1386  // don't use the constructor which normalizes!
1387  QgsRectangle rect;
1388  rect.setXMinimum( westLon );
1389  rect.setYMinimum( southLat );
1390  rect.setXMaximum( eastLon );
1391  rect.setYMaximum( northLat );
1392  return rect;
1393 }
1394 
1396 {
1397  if ( !d->mIsValid )
1398  return;
1399 
1400  if ( d->mSrsId >= USER_CRS_START_ID )
1401  {
1402  // user CRS, so update to new definition
1403  createFromSrsId( d->mSrsId );
1404  }
1405  else
1406  {
1407  // nothing to do -- only user CRS definitions can be changed
1408  }
1409 }
1410 
1411 void QgsCoordinateReferenceSystem::setProjString( const QString &proj4String )
1412 {
1413  d.detach();
1414  d->mProj4 = proj4String;
1415  d->mWktPreferred.clear();
1416 
1417  QgsLocaleNumC l;
1418  QString trimmed = proj4String.trimmed();
1419 
1420  trimmed += QLatin1String( " +type=crs" );
1422 
1423  {
1424  d->setPj( QgsProjUtils::proj_pj_unique_ptr( proj_create( ctx, trimmed.toLatin1().constData() ) ) );
1425  }
1426 
1427  if ( !d->hasPj() )
1428  {
1429 #ifdef QGISDEBUG
1430  const int errNo = proj_context_errno( ctx );
1431  QgsDebugMsg( QStringLiteral( "proj string rejected: %1" ).arg( proj_errno_string( errNo ) ) );
1432 #endif
1433  d->mIsValid = false;
1434  }
1435  else
1436  {
1437  d->mEllipsoidAcronym.clear();
1438  d->mIsValid = true;
1439  }
1440 
1441  setMapUnits();
1442 }
1443 
1444 bool QgsCoordinateReferenceSystem::setWktString( const QString &wkt )
1445 {
1446  bool res = false;
1447  d->mIsValid = false;
1448  d->mWktPreferred.clear();
1449 
1450  PROJ_STRING_LIST warnings = nullptr;
1451  PROJ_STRING_LIST grammerErrors = nullptr;
1452  {
1453  d->setPj( QgsProjUtils::proj_pj_unique_ptr( proj_create_from_wkt( QgsProjContext::get(), wkt.toLatin1().constData(), nullptr, &warnings, &grammerErrors ) ) );
1454  }
1455 
1456  res = d->hasPj();
1457  if ( !res )
1458  {
1459  QgsDebugMsg( QStringLiteral( "\n---------------------------------------------------------------" ) );
1460  QgsDebugMsg( QStringLiteral( "This CRS could *** NOT *** be set from the supplied Wkt " ) );
1461  QgsDebugMsg( "INPUT: " + wkt );
1462  for ( auto iter = warnings; iter && *iter; ++iter )
1463  QgsDebugMsg( *iter );
1464  for ( auto iter = grammerErrors; iter && *iter; ++iter )
1465  QgsDebugMsg( *iter );
1466  QgsDebugMsg( QStringLiteral( "---------------------------------------------------------------\n" ) );
1467  }
1468  proj_string_list_destroy( warnings );
1469  proj_string_list_destroy( grammerErrors );
1470 
1471  QgsReadWriteLocker locker( *sProj4CacheLock(), QgsReadWriteLocker::Unlocked );
1472  if ( !res )
1473  {
1474  locker.changeMode( QgsReadWriteLocker::Write );
1475  if ( !sDisableWktCache )
1476  sWktCache()->insert( wkt, *this );
1477  return d->mIsValid;
1478  }
1479 
1480  if ( d->hasPj() )
1481  {
1482  // try 1 - maybe we can directly grab the auth name and code from the crs already?
1483  QString authName( proj_get_id_auth_name( d->threadLocalProjObject(), 0 ) );
1484  QString authCode( proj_get_id_code( d->threadLocalProjObject(), 0 ) );
1485 
1486  if ( authName.isEmpty() || authCode.isEmpty() )
1487  {
1488  // try 2, use proj's identify method and see if there's a nice candidate we can use
1489  QgsProjUtils::identifyCrs( d->threadLocalProjObject(), authName, authCode );
1490  }
1491 
1492  if ( !authName.isEmpty() && !authCode.isEmpty() )
1493  {
1494  if ( loadFromAuthCode( authName, authCode ) )
1495  {
1496  locker.changeMode( QgsReadWriteLocker::Write );
1497  if ( !sDisableWktCache )
1498  sWktCache()->insert( wkt, *this );
1499  return d->mIsValid;
1500  }
1501  }
1502  else
1503  {
1504  // Still a valid CRS, just not a known one
1505  d->mIsValid = true;
1506  d->mDescription = QString( proj_get_name( d->threadLocalProjObject() ) );
1507  }
1508  setMapUnits();
1509  }
1510 
1511  return d->mIsValid;
1512 }
1513 
1514 void QgsCoordinateReferenceSystem::setMapUnits()
1515 {
1516  if ( !d->mIsValid )
1517  {
1518  d->mMapUnits = QgsUnitTypes::DistanceUnknownUnit;
1519  return;
1520  }
1521 
1522  if ( !d->hasPj() )
1523  {
1524  d->mMapUnits = QgsUnitTypes::DistanceUnknownUnit;
1525  return;
1526  }
1527 
1528  PJ_CONTEXT *context = QgsProjContext::get();
1529  QgsProjUtils::proj_pj_unique_ptr crs( QgsProjUtils::crsToSingleCrs( d->threadLocalProjObject() ) );
1530  QgsProjUtils::proj_pj_unique_ptr coordinateSystem( proj_crs_get_coordinate_system( context, crs.get() ) );
1531  if ( !coordinateSystem )
1532  {
1533  d->mMapUnits = QgsUnitTypes::DistanceUnknownUnit;
1534  return;
1535  }
1536 
1537  const int axisCount = proj_cs_get_axis_count( context, coordinateSystem.get() );
1538  if ( axisCount > 0 )
1539  {
1540  const char *outUnitName = nullptr;
1541  // Read only first axis
1542  proj_cs_get_axis_info( context, coordinateSystem.get(), 0,
1543  nullptr,
1544  nullptr,
1545  nullptr,
1546  nullptr,
1547  &outUnitName,
1548  nullptr,
1549  nullptr );
1550 
1551  const QString unitName( outUnitName );
1552 
1553  // proj unit names are freeform -- they differ from authority to authority :(
1554  // see https://lists.osgeo.org/pipermail/proj/2019-April/008444.html
1555  if ( unitName.compare( QLatin1String( "degree" ), Qt::CaseInsensitive ) == 0 ||
1556  unitName.compare( QLatin1String( "degree minute second" ), Qt::CaseInsensitive ) == 0 ||
1557  unitName.compare( QLatin1String( "degree minute second hemisphere" ), Qt::CaseInsensitive ) == 0 ||
1558  unitName.compare( QLatin1String( "degree minute" ), Qt::CaseInsensitive ) == 0 ||
1559  unitName.compare( QLatin1String( "degree hemisphere" ), Qt::CaseInsensitive ) == 0 ||
1560  unitName.compare( QLatin1String( "degree minute hemisphere" ), Qt::CaseInsensitive ) == 0 ||
1561  unitName.compare( QLatin1String( "hemisphere degree" ), Qt::CaseInsensitive ) == 0 ||
1562  unitName.compare( QLatin1String( "hemisphere degree minute" ), Qt::CaseInsensitive ) == 0 ||
1563  unitName.compare( QLatin1String( "hemisphere degree minute second" ), Qt::CaseInsensitive ) == 0 ||
1564  unitName.compare( QLatin1String( "degree (supplier to define representation)" ), Qt::CaseInsensitive ) == 0 )
1565  d->mMapUnits = QgsUnitTypes::DistanceDegrees;
1566  else if ( unitName.compare( QLatin1String( "metre" ), Qt::CaseInsensitive ) == 0
1567  || unitName.compare( QLatin1String( "m" ), Qt::CaseInsensitive ) == 0
1568  || unitName.compare( QLatin1String( "meter" ), Qt::CaseInsensitive ) == 0 )
1569  d->mMapUnits = QgsUnitTypes::DistanceMeters;
1570  // we don't differentiate between these, suck it imperial users!
1571  else if ( unitName.compare( QLatin1String( "US survey foot" ), Qt::CaseInsensitive ) == 0 ||
1572  unitName.compare( QLatin1String( "foot" ), Qt::CaseInsensitive ) == 0 )
1573  d->mMapUnits = QgsUnitTypes::DistanceFeet;
1574  else if ( unitName.compare( QLatin1String( "kilometre" ), Qt::CaseInsensitive ) == 0 ) //#spellok
1575  d->mMapUnits = QgsUnitTypes::DistanceKilometers;
1576  else if ( unitName.compare( QLatin1String( "centimetre" ), Qt::CaseInsensitive ) == 0 ) //#spellok
1577  d->mMapUnits = QgsUnitTypes::DistanceCentimeters;
1578  else if ( unitName.compare( QLatin1String( "millimetre" ), Qt::CaseInsensitive ) == 0 ) //#spellok
1579  d->mMapUnits = QgsUnitTypes::DistanceMillimeters;
1580  else if ( unitName.compare( QLatin1String( "Statute mile" ), Qt::CaseInsensitive ) == 0 )
1581  d->mMapUnits = QgsUnitTypes::DistanceMiles;
1582  else if ( unitName.compare( QLatin1String( "nautical mile" ), Qt::CaseInsensitive ) == 0 )
1583  d->mMapUnits = QgsUnitTypes::DistanceNauticalMiles;
1584  else if ( unitName.compare( QLatin1String( "yard" ), Qt::CaseInsensitive ) == 0 )
1585  d->mMapUnits = QgsUnitTypes::DistanceYards;
1586  // TODO - maybe more values to handle here?
1587  else
1588  d->mMapUnits = QgsUnitTypes::DistanceUnknownUnit;
1589  return;
1590  }
1591  else
1592  {
1593  d->mMapUnits = QgsUnitTypes::DistanceUnknownUnit;
1594  return;
1595  }
1596 }
1597 
1598 
1600 {
1601  if ( d->mEllipsoidAcronym.isNull() || d->mProjectionAcronym.isNull()
1602  || !d->mIsValid )
1603  {
1604  QgsDebugMsgLevel( "QgsCoordinateReferenceSystem::findMatchingProj will only "
1605  "work if prj acr ellipsoid acr and proj4string are set"
1606  " and the current projection is valid!", 4 );
1607  return 0;
1608  }
1609 
1610  sqlite3_database_unique_ptr database;
1611  sqlite3_statement_unique_ptr statement;
1612  int myResult;
1613 
1614  // Set up the query to retrieve the projection information
1615  // needed to populate the list
1616  QString mySql = QString( "select srs_id,parameters from tbl_srs where "
1617  "projection_acronym=%1 and ellipsoid_acronym=%2 order by deprecated" )
1618  .arg( QgsSqliteUtils::quotedString( d->mProjectionAcronym ),
1619  QgsSqliteUtils::quotedString( d->mEllipsoidAcronym ) );
1620  // Get the full path name to the sqlite3 spatial reference database.
1621  QString myDatabaseFileName = QgsApplication::srsDatabaseFilePath();
1622 
1623  //check the db is available
1624  myResult = openDatabase( myDatabaseFileName, database );
1625  if ( myResult != SQLITE_OK )
1626  {
1627  return 0;
1628  }
1629 
1630  statement = database.prepare( mySql, myResult );
1631  if ( myResult == SQLITE_OK )
1632  {
1633 
1634  while ( statement.step() == SQLITE_ROW )
1635  {
1636  QString mySrsId = statement.columnAsText( 0 );
1637  QString myProj4String = statement.columnAsText( 1 );
1638  if ( toProj() == myProj4String.trimmed() )
1639  {
1640  return mySrsId.toLong();
1641  }
1642  }
1643  }
1644 
1645  //
1646  // Try the users db now
1647  //
1648 
1649  myDatabaseFileName = QgsApplication::qgisUserDatabaseFilePath();
1650  //check the db is available
1651  myResult = openDatabase( myDatabaseFileName, database );
1652  if ( myResult != SQLITE_OK )
1653  {
1654  return 0;
1655  }
1656 
1657  statement = database.prepare( mySql, myResult );
1658 
1659  if ( myResult == SQLITE_OK )
1660  {
1661  while ( statement.step() == SQLITE_ROW )
1662  {
1663  QString mySrsId = statement.columnAsText( 0 );
1664  QString myProj4String = statement.columnAsText( 1 );
1665  if ( toProj() == myProj4String.trimmed() )
1666  {
1667  return mySrsId.toLong();
1668  }
1669  }
1670  }
1671 
1672  return 0;
1673 }
1674 
1676 {
1677  // shortcut
1678  if ( d == srs.d )
1679  return true;
1680 
1681  if ( !d->mIsValid && !srs.d->mIsValid )
1682  return true;
1683 
1684  if ( !d->mIsValid || !srs.d->mIsValid )
1685  return false;
1686 
1687  if ( !qgsNanCompatibleEquals( d->mCoordinateEpoch, srs.d->mCoordinateEpoch ) )
1688  return false;
1689 
1690  const bool isUser = d->mSrsId >= USER_CRS_START_ID;
1691  const bool otherIsUser = srs.d->mSrsId >= USER_CRS_START_ID;
1692  if ( isUser != otherIsUser )
1693  return false;
1694 
1695  // we can't directly compare authid for user crses -- the actual definition of these may have changed
1696  if ( !isUser && ( !d->mAuthId.isEmpty() || !srs.d->mAuthId.isEmpty() ) )
1697  return d->mAuthId == srs.d->mAuthId;
1698 
1699  return toWkt( WKT_PREFERRED ) == srs.toWkt( WKT_PREFERRED );
1700 }
1701 
1703 {
1704  return !( *this == srs );
1705 }
1706 
1707 QString QgsCoordinateReferenceSystem::toWkt( WktVariant variant, bool multiline, int indentationWidth ) const
1708 {
1709  if ( PJ *obj = d->threadLocalProjObject() )
1710  {
1711  const bool isDefaultPreferredFormat = variant == WKT_PREFERRED && !multiline;
1712  if ( isDefaultPreferredFormat && !d->mWktPreferred.isEmpty() )
1713  {
1714  // can use cached value
1715  return d->mWktPreferred;
1716  }
1717 
1718  PJ_WKT_TYPE type = PJ_WKT1_GDAL;
1719  switch ( variant )
1720  {
1721  case WKT1_GDAL:
1722  type = PJ_WKT1_GDAL;
1723  break;
1724  case WKT1_ESRI:
1725  type = PJ_WKT1_ESRI;
1726  break;
1727  case WKT2_2015:
1728  type = PJ_WKT2_2015;
1729  break;
1730  case WKT2_2015_SIMPLIFIED:
1731  type = PJ_WKT2_2015_SIMPLIFIED;
1732  break;
1733  case WKT2_2019:
1734  type = PJ_WKT2_2019;
1735  break;
1736  case WKT2_2019_SIMPLIFIED:
1737  type = PJ_WKT2_2019_SIMPLIFIED;
1738  break;
1739  }
1740 
1741  const QByteArray multiLineOption = QStringLiteral( "MULTILINE=%1" ).arg( multiline ? QStringLiteral( "YES" ) : QStringLiteral( "NO" ) ).toLocal8Bit();
1742  const QByteArray indentatationWidthOption = QStringLiteral( "INDENTATION_WIDTH=%1" ).arg( multiline ? QString::number( indentationWidth ) : QStringLiteral( "0" ) ).toLocal8Bit();
1743  const char *const options[] = {multiLineOption.constData(), indentatationWidthOption.constData(), nullptr};
1744  QString res = proj_as_wkt( QgsProjContext::get(), obj, type, options );
1745 
1746  if ( isDefaultPreferredFormat )
1747  {
1748  // cache result for later use
1749  d->mWktPreferred = res;
1750  }
1751 
1752  return res;
1753  }
1754  return QString();
1755 }
1756 
1757 bool QgsCoordinateReferenceSystem::readXml( const QDomNode &node )
1758 {
1759  d.detach();
1760  bool result = true;
1761  QDomNode srsNode = node.namedItem( QStringLiteral( "spatialrefsys" ) );
1762 
1763  if ( ! srsNode.isNull() )
1764  {
1765  bool initialized = false;
1766 
1767  bool ok = false;
1768  long srsid = srsNode.namedItem( QStringLiteral( "srsid" ) ).toElement().text().toLong( &ok );
1769 
1770  QDomNode node;
1771 
1772  if ( ok && srsid > 0 && srsid < USER_CRS_START_ID )
1773  {
1774  node = srsNode.namedItem( QStringLiteral( "authid" ) );
1775  if ( !node.isNull() )
1776  {
1777  createFromOgcWmsCrs( node.toElement().text() );
1778  if ( isValid() )
1779  {
1780  initialized = true;
1781  }
1782  }
1783 
1784  if ( !initialized )
1785  {
1786  node = srsNode.namedItem( QStringLiteral( "epsg" ) );
1787  if ( !node.isNull() )
1788  {
1789  operator=( QgsCoordinateReferenceSystem::fromEpsgId( node.toElement().text().toLong() ) );
1790  if ( isValid() )
1791  {
1792  initialized = true;
1793  }
1794  }
1795  }
1796  }
1797 
1798  // if wkt is present, prefer that since it's lossless (unlike proj4 strings)
1799  if ( !initialized )
1800  {
1801  // before doing anything, we grab and set the stored CRS name (description).
1802  // this way if the stored CRS doesn't match anything available locally (i.e. from Proj's db
1803  // or the user's custom CRS list), then we will correctly show the CRS with its original
1804  // name (instead of just "custom crs")
1805  const QString description = srsNode.namedItem( QStringLiteral( "description" ) ).toElement().text();
1806 
1807  const QString wkt = srsNode.namedItem( QStringLiteral( "wkt" ) ).toElement().text();
1808  initialized = createFromWktInternal( wkt, description );
1809  }
1810 
1811  if ( !initialized )
1812  {
1813  node = srsNode.namedItem( QStringLiteral( "proj4" ) );
1814  const QString proj4 = node.toElement().text();
1815  initialized = createFromProj( proj4 );
1816  }
1817 
1818  if ( !initialized )
1819  {
1820  // Setting from elements one by one
1821  node = srsNode.namedItem( QStringLiteral( "proj4" ) );
1822  const QString proj4 = node.toElement().text();
1823  if ( !proj4.trimmed().isEmpty() )
1824  setProjString( node.toElement().text() );
1825 
1826  node = srsNode.namedItem( QStringLiteral( "srsid" ) );
1827  d->mSrsId = node.toElement().text().toLong();
1828 
1829  node = srsNode.namedItem( QStringLiteral( "srid" ) );
1830  d->mSRID = node.toElement().text().toLong();
1831 
1832  node = srsNode.namedItem( QStringLiteral( "authid" ) );
1833  d->mAuthId = node.toElement().text();
1834 
1835  node = srsNode.namedItem( QStringLiteral( "description" ) );
1836  d->mDescription = node.toElement().text();
1837 
1838  node = srsNode.namedItem( QStringLiteral( "projectionacronym" ) );
1839  d->mProjectionAcronym = node.toElement().text();
1840 
1841  node = srsNode.namedItem( QStringLiteral( "ellipsoidacronym" ) );
1842  d->mEllipsoidAcronym = node.toElement().text();
1843 
1844  node = srsNode.namedItem( QStringLiteral( "geographicflag" ) );
1845  d->mIsGeographic = node.toElement().text() == QLatin1String( "true" );
1846 
1847  d->mWktPreferred.clear();
1848 
1849  //make sure the map units have been set
1850  setMapUnits();
1851  }
1852 
1853  const QString epoch = srsNode.toElement().attribute( QStringLiteral( "coordinateEpoch" ) );
1854  if ( !epoch.isEmpty() )
1855  {
1856  bool epochOk = false;
1857  d->mCoordinateEpoch = epoch.toDouble( &epochOk );
1858  if ( !epochOk )
1859  d->mCoordinateEpoch = std::numeric_limits< double >::quiet_NaN();
1860  }
1861  else
1862  {
1863  d->mCoordinateEpoch = std::numeric_limits< double >::quiet_NaN();
1864  }
1865 
1866  mNativeFormat = qgsEnumKeyToValue<Qgis::CrsDefinitionFormat>( srsNode.toElement().attribute( QStringLiteral( "nativeFormat" ) ), Qgis::CrsDefinitionFormat::Wkt );
1867  }
1868  else
1869  {
1870  // Return empty CRS if none was found in the XML.
1871  d = new QgsCoordinateReferenceSystemPrivate();
1872  result = false;
1873  }
1874  return result;
1875 }
1876 
1877 bool QgsCoordinateReferenceSystem::writeXml( QDomNode &node, QDomDocument &doc ) const
1878 {
1879  QDomElement layerNode = node.toElement();
1880  QDomElement srsElement = doc.createElement( QStringLiteral( "spatialrefsys" ) );
1881 
1882  srsElement.setAttribute( QStringLiteral( "nativeFormat" ), qgsEnumValueToKey<Qgis::CrsDefinitionFormat>( mNativeFormat ) );
1883 
1884  if ( std::isfinite( d->mCoordinateEpoch ) )
1885  {
1886  srsElement.setAttribute( QStringLiteral( "coordinateEpoch" ), d->mCoordinateEpoch );
1887  }
1888 
1889  QDomElement wktElement = doc.createElement( QStringLiteral( "wkt" ) );
1890  wktElement.appendChild( doc.createTextNode( toWkt( WKT_PREFERRED ) ) );
1891  srsElement.appendChild( wktElement );
1892 
1893  QDomElement proj4Element = doc.createElement( QStringLiteral( "proj4" ) );
1894  proj4Element.appendChild( doc.createTextNode( toProj() ) );
1895  srsElement.appendChild( proj4Element );
1896 
1897  QDomElement srsIdElement = doc.createElement( QStringLiteral( "srsid" ) );
1898  srsIdElement.appendChild( doc.createTextNode( QString::number( srsid() ) ) );
1899  srsElement.appendChild( srsIdElement );
1900 
1901  QDomElement sridElement = doc.createElement( QStringLiteral( "srid" ) );
1902  sridElement.appendChild( doc.createTextNode( QString::number( postgisSrid() ) ) );
1903  srsElement.appendChild( sridElement );
1904 
1905  QDomElement authidElement = doc.createElement( QStringLiteral( "authid" ) );
1906  authidElement.appendChild( doc.createTextNode( authid() ) );
1907  srsElement.appendChild( authidElement );
1908 
1909  QDomElement descriptionElement = doc.createElement( QStringLiteral( "description" ) );
1910  descriptionElement.appendChild( doc.createTextNode( description() ) );
1911  srsElement.appendChild( descriptionElement );
1912 
1913  QDomElement projectionAcronymElement = doc.createElement( QStringLiteral( "projectionacronym" ) );
1914  projectionAcronymElement.appendChild( doc.createTextNode( projectionAcronym() ) );
1915  srsElement.appendChild( projectionAcronymElement );
1916 
1917  QDomElement ellipsoidAcronymElement = doc.createElement( QStringLiteral( "ellipsoidacronym" ) );
1918  ellipsoidAcronymElement.appendChild( doc.createTextNode( ellipsoidAcronym() ) );
1919  srsElement.appendChild( ellipsoidAcronymElement );
1920 
1921  QDomElement geographicFlagElement = doc.createElement( QStringLiteral( "geographicflag" ) );
1922  QString geoFlagText = QStringLiteral( "false" );
1923  if ( isGeographic() )
1924  {
1925  geoFlagText = QStringLiteral( "true" );
1926  }
1927 
1928  geographicFlagElement.appendChild( doc.createTextNode( geoFlagText ) );
1929  srsElement.appendChild( geographicFlagElement );
1930 
1931  layerNode.appendChild( srsElement );
1932 
1933  return true;
1934 }
1935 
1936 //
1937 // Static helper methods below this point only please!
1938 //
1939 
1940 
1941 // Returns the whole proj4 string for the selected srsid
1942 //this is a static method! NOTE I've made it private for now to reduce API clutter TS
1943 QString QgsCoordinateReferenceSystem::projFromSrsId( const int srsId )
1944 {
1945  QString myDatabaseFileName;
1946  QString myProjString;
1947  QString mySql = QStringLiteral( "select parameters from tbl_srs where srs_id = %1 order by deprecated" ).arg( srsId );
1948 
1949  //
1950  // Determine if this is a user projection or a system on
1951  // user projection defs all have srs_id >= 100000
1952  //
1953  if ( srsId >= USER_CRS_START_ID )
1954  {
1955  myDatabaseFileName = QgsApplication::qgisUserDatabaseFilePath();
1956  QFileInfo myFileInfo;
1957  myFileInfo.setFile( myDatabaseFileName );
1958  if ( !myFileInfo.exists() ) //its unlikely that this condition will ever be reached
1959  {
1960  QgsDebugMsg( QStringLiteral( "users qgis.db not found" ) );
1961  return QString();
1962  }
1963  }
1964  else //must be a system projection then
1965  {
1966  myDatabaseFileName = QgsApplication::srsDatabaseFilePath();
1967  }
1968 
1969  sqlite3_database_unique_ptr database;
1970  sqlite3_statement_unique_ptr statement;
1971 
1972  int rc;
1973  rc = openDatabase( myDatabaseFileName, database );
1974  if ( rc )
1975  {
1976  return QString();
1977  }
1978 
1979  statement = database.prepare( mySql, rc );
1980 
1981  if ( rc == SQLITE_OK )
1982  {
1983  if ( statement.step() == SQLITE_ROW )
1984  {
1985  myProjString = statement.columnAsText( 0 );
1986  }
1987  }
1988 
1989  return myProjString;
1990 }
1991 
1992 int QgsCoordinateReferenceSystem::openDatabase( const QString &path, sqlite3_database_unique_ptr &database, bool readonly )
1993 {
1994  int myResult;
1995  if ( readonly )
1996  myResult = database.open_v2( path, SQLITE_OPEN_READONLY, nullptr );
1997  else
1998  myResult = database.open( path );
1999 
2000  if ( myResult != SQLITE_OK )
2001  {
2002  QgsDebugMsg( "Can't open database: " + database.errorMessage() );
2003  // XXX This will likely never happen since on open, sqlite creates the
2004  // database if it does not exist.
2005  // ... unfortunately it happens on Windows
2006  QgsMessageLog::logMessage( QObject::tr( "Could not open CRS database %1\nError(%2): %3" )
2007  .arg( path )
2008  .arg( myResult )
2009  .arg( database.errorMessage() ), QObject::tr( "CRS" ) );
2010  }
2011  return myResult;
2012 }
2013 
2015 {
2016  sCustomSrsValidation = f;
2017 }
2018 
2020 {
2021  return sCustomSrsValidation;
2022 }
2023 
2024 void QgsCoordinateReferenceSystem::debugPrint()
2025 {
2026  QgsDebugMsg( QStringLiteral( "***SpatialRefSystem***" ) );
2027  QgsDebugMsg( "* Valid : " + ( d->mIsValid ? QString( "true" ) : QString( "false" ) ) );
2028  QgsDebugMsg( "* SrsId : " + QString::number( d->mSrsId ) );
2029  QgsDebugMsg( "* Proj4 : " + toProj() );
2030  QgsDebugMsg( "* WKT : " + toWkt( WKT_PREFERRED ) );
2031  QgsDebugMsg( "* Desc. : " + d->mDescription );
2033  {
2034  QgsDebugMsg( QStringLiteral( "* Units : meters" ) );
2035  }
2036  else if ( mapUnits() == QgsUnitTypes::DistanceFeet )
2037  {
2038  QgsDebugMsg( QStringLiteral( "* Units : feet" ) );
2039  }
2040  else if ( mapUnits() == QgsUnitTypes::DistanceDegrees )
2041  {
2042  QgsDebugMsg( QStringLiteral( "* Units : degrees" ) );
2043  }
2044 }
2045 
2047 {
2048  mValidationHint = html;
2049 }
2050 
2052 {
2053  return mValidationHint;
2054 }
2055 
2057 {
2059 }
2060 
2062 {
2063  mNativeFormat = format;
2064 }
2065 
2067 {
2068  return mNativeFormat;
2069 }
2070 
2071 long QgsCoordinateReferenceSystem::getRecordCount()
2072 {
2073  sqlite3_database_unique_ptr database;
2074  sqlite3_statement_unique_ptr statement;
2075  int myResult;
2076  long myRecordCount = 0;
2077  //check the db is available
2078  myResult = database.open_v2( QgsApplication::qgisUserDatabaseFilePath(), SQLITE_OPEN_READONLY, nullptr );
2079  if ( myResult != SQLITE_OK )
2080  {
2081  QgsDebugMsg( QStringLiteral( "Can't open database: %1" ).arg( database.errorMessage() ) );
2082  return 0;
2083  }
2084  // Set up the query to retrieve the projection information needed to populate the ELLIPSOID list
2085  QString mySql = QStringLiteral( "select count(*) from tbl_srs" );
2086  statement = database.prepare( mySql, myResult );
2087  if ( myResult == SQLITE_OK )
2088  {
2089  if ( statement.step() == SQLITE_ROW )
2090  {
2091  QString myRecordCountString = statement.columnAsText( 0 );
2092  myRecordCount = myRecordCountString.toLong();
2093  }
2094  }
2095  return myRecordCount;
2096 }
2097 
2099 {
2100  PJ_CONTEXT *pjContext = QgsProjContext::get();
2101  bool isGeographic = false;
2102  QgsProjUtils::proj_pj_unique_ptr coordinateSystem( proj_crs_get_coordinate_system( pjContext, crs ) );
2103  if ( coordinateSystem )
2104  {
2105  const int axisCount = proj_cs_get_axis_count( pjContext, coordinateSystem.get() );
2106  if ( axisCount > 0 )
2107  {
2108  const char *outUnitAuthName = nullptr;
2109  const char *outUnitAuthCode = nullptr;
2110  // Read only first axis
2111  proj_cs_get_axis_info( pjContext, coordinateSystem.get(), 0,
2112  nullptr,
2113  nullptr,
2114  nullptr,
2115  nullptr,
2116  nullptr,
2117  &outUnitAuthName,
2118  &outUnitAuthCode );
2119 
2120  if ( outUnitAuthName && outUnitAuthCode )
2121  {
2122  const char *unitCategory = nullptr;
2123  if ( proj_uom_get_info_from_database( pjContext, outUnitAuthName, outUnitAuthCode, nullptr, nullptr, &unitCategory ) )
2124  {
2125  isGeographic = QString( unitCategory ).compare( QLatin1String( "angular" ), Qt::CaseInsensitive ) == 0;
2126  }
2127  }
2128  }
2129  }
2130  return isGeographic;
2131 }
2132 
2133 void getOperationAndEllipsoidFromProjString( const QString &proj, QString &operation, QString &ellipsoid )
2134 {
2135  thread_local const QRegularExpression projRegExp( QStringLiteral( "\\+proj=(\\S+)" ) );
2136  const QRegularExpressionMatch projMatch = projRegExp.match( proj );
2137  if ( !projMatch.hasMatch() )
2138  {
2139  QgsDebugMsgLevel( QStringLiteral( "no +proj argument found [%2]" ).arg( proj ), 2 );
2140  return;
2141  }
2142  operation = projMatch.captured( 1 );
2143 
2144  const QRegularExpressionMatch ellipseMatch = projRegExp.match( proj );
2145  if ( ellipseMatch.hasMatch() )
2146  {
2147  ellipsoid = ellipseMatch.captured( 1 );
2148  }
2149  else
2150  {
2151  // satisfy not null constraint on ellipsoid_acronym field
2152  // possibly we should drop the constraint, yet the crses with missing ellipsoid_acronym are malformed
2153  // and will result in oddities within other areas of QGIS (e.g. project ellipsoid won't be correctly
2154  // set for these CRSes). Better just hack around and make the constraint happy for now,
2155  // and hope that the definitions get corrected in future.
2156  ellipsoid = "";
2157  }
2158 }
2159 
2160 
2161 bool QgsCoordinateReferenceSystem::loadFromAuthCode( const QString &auth, const QString &code )
2162 {
2163  d.detach();
2164  d->mIsValid = false;
2165  d->mWktPreferred.clear();
2166 
2167  PJ_CONTEXT *pjContext = QgsProjContext::get();
2168  QgsProjUtils::proj_pj_unique_ptr crs( proj_create_from_database( pjContext, auth.toUtf8().constData(), code.toUtf8().constData(), PJ_CATEGORY_CRS, false, nullptr ) );
2169  if ( !crs )
2170  {
2171  return false;
2172  }
2173 
2174  switch ( proj_get_type( crs.get() ) )
2175  {
2176  case PJ_TYPE_VERTICAL_CRS:
2177  return false;
2178 
2179  default:
2180  break;
2181  }
2182 
2184 
2185  QString proj4 = getFullProjString( crs.get() );
2186  proj4.replace( QLatin1String( "+type=crs" ), QString() );
2187  proj4 = proj4.trimmed();
2188 
2189  d->mIsValid = true;
2190  d->mProj4 = proj4;
2191  d->mWktPreferred.clear();
2192  d->mDescription = QString( proj_get_name( crs.get() ) );
2193  d->mAuthId = QStringLiteral( "%1:%2" ).arg( auth, code );
2194  d->mIsGeographic = testIsGeographic( crs.get() );
2195  d->mAxisInvertedDirty = true;
2196  QString operation;
2197  QString ellipsoid;
2199  d->mProjectionAcronym = operation;
2200  d->mEllipsoidAcronym.clear();
2201  d->setPj( std::move( crs ) );
2202 
2203  const QString dbVals = sAuthIdToQgisSrsIdMap.value( QStringLiteral( "%1:%2" ).arg( auth, code ).toUpper() );
2204  if ( !dbVals.isEmpty() )
2205  {
2206  const QStringList parts = dbVals.split( ',' );
2207  d->mSrsId = parts.at( 0 ).toInt();
2208  d->mSRID = parts.at( 1 ).toInt();
2209  }
2210 
2211  setMapUnits();
2212 
2213  return true;
2214 }
2215 
2216 QList<long> QgsCoordinateReferenceSystem::userSrsIds()
2217 {
2218  QList<long> results;
2219  // check user defined projection database
2220  const QString db = QgsApplication::qgisUserDatabaseFilePath();
2221 
2222  QFileInfo myInfo( db );
2223  if ( !myInfo.exists() )
2224  {
2225  QgsDebugMsg( "failed : " + db + " does not exist!" );
2226  return results;
2227  }
2228 
2229  sqlite3_database_unique_ptr database;
2230  sqlite3_statement_unique_ptr statement;
2231 
2232  //check the db is available
2233  int result = openDatabase( db, database );
2234  if ( result != SQLITE_OK )
2235  {
2236  QgsDebugMsg( "failed : " + db + " could not be opened!" );
2237  return results;
2238  }
2239 
2240  QString sql = QStringLiteral( "select srs_id from tbl_srs where srs_id >= %1" ).arg( USER_CRS_START_ID );
2241  int rc;
2242  statement = database.prepare( sql, rc );
2243  while ( true )
2244  {
2245  int ret = statement.step();
2246 
2247  if ( ret == SQLITE_DONE )
2248  {
2249  // there are no more rows to fetch - we can stop looping
2250  break;
2251  }
2252 
2253  if ( ret == SQLITE_ROW )
2254  {
2255  results.append( statement.columnAsInt64( 0 ) );
2256  }
2257  else
2258  {
2259  QgsMessageLog::logMessage( QObject::tr( "SQLite error: %2\nSQL: %1" ).arg( sql, sqlite3_errmsg( database.get() ) ), QObject::tr( "SpatiaLite" ) );
2260  break;
2261  }
2262  }
2263 
2264  return results;
2265 }
2266 
2267 long QgsCoordinateReferenceSystem::matchToUserCrs() const
2268 {
2269  PJ *obj = d->threadLocalProjObject();
2270  if ( !obj )
2271  return 0;
2272 
2273  const QList< long > ids = userSrsIds();
2274  for ( long id : ids )
2275  {
2277  if ( candidate.projObject() && proj_is_equivalent_to( obj, candidate.projObject(), PJ_COMP_EQUIVALENT ) )
2278  {
2279  return id;
2280  }
2281  }
2282  return 0;
2283 }
2284 
2285 static void sync_db_proj_logger( void * /* user_data */, int level, const char *message )
2286 {
2287 #ifndef QGISDEBUG
2288  Q_UNUSED( message )
2289 #endif
2290  if ( level == PJ_LOG_ERROR )
2291  {
2292  QgsDebugMsgLevel( QStringLiteral( "PROJ: %1" ).arg( message ), 2 );
2293  }
2294  else if ( level == PJ_LOG_DEBUG )
2295  {
2296  QgsDebugMsgLevel( QStringLiteral( "PROJ: %1" ).arg( message ), 3 );
2297  }
2298 }
2299 
2301 {
2302  setlocale( LC_ALL, "C" );
2303  QString dbFilePath = QgsApplication::srsDatabaseFilePath();
2304 
2305  int inserted = 0, updated = 0, deleted = 0, errors = 0;
2306 
2307  QgsDebugMsgLevel( QStringLiteral( "Load srs db from: %1" ).arg( QgsApplication::srsDatabaseFilePath().toLocal8Bit().constData() ), 4 );
2308 
2309  sqlite3_database_unique_ptr database;
2310  if ( database.open( dbFilePath ) != SQLITE_OK )
2311  {
2312  QgsDebugMsg( QStringLiteral( "Could not open database: %1 (%2)\n" ).arg( QgsApplication::srsDatabaseFilePath(), database.errorMessage() ) );
2313  return -1;
2314  }
2315 
2316  if ( sqlite3_exec( database.get(), "BEGIN TRANSACTION", nullptr, nullptr, nullptr ) != SQLITE_OK )
2317  {
2318  QgsDebugMsg( QStringLiteral( "Could not begin transaction: %1 (%2)\n" ).arg( QgsApplication::srsDatabaseFilePath(), database.errorMessage() ) );
2319  return -1;
2320  }
2321 
2322  sqlite3_statement_unique_ptr statement;
2323  int result;
2324  char *errMsg = nullptr;
2325 
2326  if ( sqlite3_exec( database.get(), "create table tbl_info (proj_major INT, proj_minor INT, proj_patch INT)", nullptr, nullptr, nullptr ) == SQLITE_OK )
2327  {
2328  QString sql = QStringLiteral( "INSERT INTO tbl_info(proj_major, proj_minor, proj_patch) VALUES (%1, %2,%3)" )
2329  .arg( QString::number( PROJ_VERSION_MAJOR ),
2330  QString::number( PROJ_VERSION_MINOR ),
2331  QString::number( PROJ_VERSION_PATCH ) );
2332  if ( sqlite3_exec( database.get(), sql.toUtf8(), nullptr, nullptr, &errMsg ) != SQLITE_OK )
2333  {
2334  QgsDebugMsg( QStringLiteral( "Could not execute: %1 [%2/%3]\n" ).arg(
2335  sql,
2336  database.errorMessage(),
2337  errMsg ? errMsg : "(unknown error)" ) );
2338  if ( errMsg )
2339  sqlite3_free( errMsg );
2340  return -1;
2341  }
2342  }
2343  else
2344  {
2345  // retrieve last update details
2346  QString sql = QStringLiteral( "SELECT proj_major, proj_minor, proj_patch FROM tbl_info" );
2347  statement = database.prepare( sql, result );
2348  if ( result != SQLITE_OK )
2349  {
2350  QgsDebugMsg( QStringLiteral( "Could not prepare: %1 [%2]\n" ).arg( sql, database.errorMessage() ) );
2351  return -1;
2352  }
2353  if ( statement.step() == SQLITE_ROW )
2354  {
2355  int major = statement.columnAsInt64( 0 );
2356  int minor = statement.columnAsInt64( 1 );
2357  int patch = statement.columnAsInt64( 2 );
2358  if ( major == PROJ_VERSION_MAJOR && minor == PROJ_VERSION_MINOR && patch == PROJ_VERSION_PATCH )
2359  // yay, nothing to do!
2360  return 0;
2361  }
2362  else
2363  {
2364  QgsDebugMsg( QStringLiteral( "Could not retrieve previous CRS sync PROJ version number" ) );
2365  return -1;
2366  }
2367  }
2368 
2369  PJ_CONTEXT *pjContext = QgsProjContext::get();
2370  // silence proj warnings
2371  proj_log_func( pjContext, nullptr, sync_db_proj_logger );
2372 
2373  PROJ_STRING_LIST authorities = proj_get_authorities_from_database( pjContext );
2374 
2375  int nextSrsId = 63561;
2376  int nextSrId = 520003561;
2377  for ( auto authIter = authorities; authIter && *authIter; ++authIter )
2378  {
2379  const QString authority( *authIter );
2380  QgsDebugMsgLevel( QStringLiteral( "Loading authority '%1'" ).arg( authority ), 2 );
2381  PROJ_STRING_LIST codes = proj_get_codes_from_database( pjContext, *authIter, PJ_TYPE_CRS, true );
2382 
2383  QStringList allCodes;
2384 
2385  for ( auto codesIter = codes; codesIter && *codesIter; ++codesIter )
2386  {
2387  const QString code( *codesIter );
2388  allCodes << QgsSqliteUtils::quotedString( code );
2389  QgsDebugMsgLevel( QStringLiteral( "Loading code '%1'" ).arg( code ), 4 );
2390  QgsProjUtils::proj_pj_unique_ptr crs( proj_create_from_database( pjContext, *authIter, *codesIter, PJ_CATEGORY_CRS, false, nullptr ) );
2391  if ( !crs )
2392  {
2393  QgsDebugMsg( QStringLiteral( "Could not load '%1:%2'" ).arg( authority, code ) );
2394  continue;
2395  }
2396 
2397  switch ( proj_get_type( crs.get() ) )
2398  {
2399  case PJ_TYPE_VERTICAL_CRS: // don't need these in the CRS db
2400  continue;
2401 
2402  default:
2403  break;
2404  }
2405 
2407 
2408  QString proj4 = getFullProjString( crs.get() );
2409  proj4.replace( QLatin1String( "+type=crs" ), QString() );
2410  proj4 = proj4.trimmed();
2411 
2412  if ( proj4.isEmpty() )
2413  {
2414  QgsDebugMsgLevel( QStringLiteral( "No proj4 for '%1:%2'" ).arg( authority, code ), 2 );
2415  // satisfy not null constraint
2416  proj4 = "";
2417  }
2418 
2419  const bool deprecated = proj_is_deprecated( crs.get() );
2420  const QString name( proj_get_name( crs.get() ) );
2421 
2422  QString sql = QStringLiteral( "SELECT parameters,description,deprecated FROM tbl_srs WHERE auth_name='%1' AND auth_id='%2'" ).arg( authority, code );
2423  statement = database.prepare( sql, result );
2424  if ( result != SQLITE_OK )
2425  {
2426  QgsDebugMsg( QStringLiteral( "Could not prepare: %1 [%2]\n" ).arg( sql, database.errorMessage() ) );
2427  continue;
2428  }
2429 
2430  QString srsProj4;
2431  QString srsDesc;
2432  bool srsDeprecated = deprecated;
2433  if ( statement.step() == SQLITE_ROW )
2434  {
2435  srsProj4 = statement.columnAsText( 0 );
2436  srsDesc = statement.columnAsText( 1 );
2437  srsDeprecated = statement.columnAsText( 2 ).toInt() != 0;
2438  }
2439 
2440  if ( !srsProj4.isEmpty() || !srsDesc.isEmpty() )
2441  {
2442  if ( proj4 != srsProj4 || name != srsDesc || deprecated != srsDeprecated )
2443  {
2444  errMsg = nullptr;
2445  sql = QStringLiteral( "UPDATE tbl_srs SET parameters=%1,description=%2,deprecated=%3 WHERE auth_name=%4 AND auth_id=%5" )
2446  .arg( QgsSqliteUtils::quotedString( proj4 ) )
2447  .arg( QgsSqliteUtils::quotedString( name ) )
2448  .arg( deprecated ? 1 : 0 )
2449  .arg( QgsSqliteUtils::quotedString( authority ), QgsSqliteUtils::quotedString( code ) );
2450 
2451  if ( sqlite3_exec( database.get(), sql.toUtf8(), nullptr, nullptr, &errMsg ) != SQLITE_OK )
2452  {
2453  QgsDebugMsg( QStringLiteral( "Could not execute: %1 [%2/%3]\n" ).arg(
2454  sql,
2455  database.errorMessage(),
2456  errMsg ? errMsg : "(unknown error)" ) );
2457  if ( errMsg )
2458  sqlite3_free( errMsg );
2459  errors++;
2460  }
2461  else
2462  {
2463  updated++;
2464  }
2465  }
2466  }
2467  else
2468  {
2469  // there's a not-null contraint on these columns, so we must use empty strings instead
2470  QString operation = "";
2471  QString ellps = "";
2473  const bool isGeographic = testIsGeographic( crs.get() );
2474 
2475  // work out srid and srsid
2476  const QString dbVals = sAuthIdToQgisSrsIdMap.value( QStringLiteral( "%1:%2" ).arg( authority, code ) );
2477  QString srsId;
2478  QString srId;
2479  if ( !dbVals.isEmpty() )
2480  {
2481  const QStringList parts = dbVals.split( ',' );
2482  srsId = parts.at( 0 );
2483  srId = parts.at( 1 );
2484  }
2485  if ( srId.isEmpty() )
2486  {
2487  srId = QString::number( nextSrId );
2488  nextSrId++;
2489  }
2490  if ( srsId.isEmpty() )
2491  {
2492  srsId = QString::number( nextSrsId );
2493  nextSrsId++;
2494  }
2495 
2496  if ( !srsId.isEmpty() )
2497  {
2498  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)" )
2499  .arg( srsId )
2500  .arg( QgsSqliteUtils::quotedString( name ),
2503  QgsSqliteUtils::quotedString( proj4 ) )
2504  .arg( srId )
2505  .arg( QgsSqliteUtils::quotedString( authority ) )
2506  .arg( QgsSqliteUtils::quotedString( code ) )
2507  .arg( isGeographic ? 1 : 0 )
2508  .arg( deprecated ? 1 : 0 );
2509  }
2510  else
2511  {
2512  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)" )
2513  .arg( QgsSqliteUtils::quotedString( name ),
2516  QgsSqliteUtils::quotedString( proj4 ) )
2517  .arg( srId )
2518  .arg( QgsSqliteUtils::quotedString( authority ) )
2519  .arg( QgsSqliteUtils::quotedString( code ) )
2520  .arg( isGeographic ? 1 : 0 )
2521  .arg( deprecated ? 1 : 0 );
2522  }
2523 
2524  errMsg = nullptr;
2525  if ( sqlite3_exec( database.get(), sql.toUtf8(), nullptr, nullptr, &errMsg ) == SQLITE_OK )
2526  {
2527  inserted++;
2528  }
2529  else
2530  {
2531  qCritical( "Could not execute: %s [%s/%s]\n",
2532  sql.toLocal8Bit().constData(),
2533  sqlite3_errmsg( database.get() ),
2534  errMsg ? errMsg : "(unknown error)" );
2535  errors++;
2536 
2537  if ( errMsg )
2538  sqlite3_free( errMsg );
2539  }
2540  }
2541  }
2542 
2543  proj_string_list_destroy( codes );
2544 
2545  const QString sql = QStringLiteral( "DELETE FROM tbl_srs WHERE auth_name='%1' AND NOT auth_id IN (%2)" ).arg( authority, allCodes.join( ',' ) );
2546  if ( sqlite3_exec( database.get(), sql.toUtf8(), nullptr, nullptr, nullptr ) == SQLITE_OK )
2547  {
2548  deleted = sqlite3_changes( database.get() );
2549  }
2550  else
2551  {
2552  errors++;
2553  qCritical( "Could not execute: %s [%s]\n",
2554  sql.toLocal8Bit().constData(),
2555  sqlite3_errmsg( database.get() ) );
2556  }
2557 
2558  }
2559  proj_string_list_destroy( authorities );
2560 
2561  QString sql = QStringLiteral( "UPDATE tbl_info set proj_major=%1,proj_minor=%2,proj_patch=%3" )
2562  .arg( QString::number( PROJ_VERSION_MAJOR ),
2563  QString::number( PROJ_VERSION_MINOR ),
2564  QString::number( PROJ_VERSION_PATCH ) );
2565  if ( sqlite3_exec( database.get(), sql.toUtf8(), nullptr, nullptr, &errMsg ) != SQLITE_OK )
2566  {
2567  QgsDebugMsg( QStringLiteral( "Could not execute: %1 [%2/%3]\n" ).arg(
2568  sql,
2569  database.errorMessage(),
2570  errMsg ? errMsg : "(unknown error)" ) );
2571  if ( errMsg )
2572  sqlite3_free( errMsg );
2573  return -1;
2574  }
2575 
2576  if ( sqlite3_exec( database.get(), "COMMIT", nullptr, nullptr, nullptr ) != SQLITE_OK )
2577  {
2578  QgsDebugMsg( QStringLiteral( "Could not commit transaction: %1 [%2]\n" ).arg(
2580  sqlite3_errmsg( database.get() ) )
2581  );
2582  return -1;
2583  }
2584 
2585 #ifdef QGISDEBUG
2586  QgsDebugMsgLevel( QStringLiteral( "CRS update (inserted:%1 updated:%2 deleted:%3 errors:%4)" ).arg( inserted ).arg( updated ).arg( deleted ).arg( errors ), 4 );
2587 #else
2588  Q_UNUSED( deleted )
2589 #endif
2590 
2591  if ( errors > 0 )
2592  return -errors;
2593  else
2594  return updated + inserted;
2595 }
2596 
2597 const QHash<QString, QgsCoordinateReferenceSystem> &QgsCoordinateReferenceSystem::stringCache()
2598 {
2599  return *sStringCache();
2600 }
2601 
2602 const QHash<QString, QgsCoordinateReferenceSystem> &QgsCoordinateReferenceSystem::projCache()
2603 {
2604  return *sProj4Cache();
2605 }
2606 
2607 const QHash<QString, QgsCoordinateReferenceSystem> &QgsCoordinateReferenceSystem::ogcCache()
2608 {
2609  return *sOgcCache();
2610 }
2611 
2612 const QHash<QString, QgsCoordinateReferenceSystem> &QgsCoordinateReferenceSystem::wktCache()
2613 {
2614  return *sWktCache();
2615 }
2616 
2617 const QHash<long, QgsCoordinateReferenceSystem> &QgsCoordinateReferenceSystem::srIdCache()
2618 {
2619  return *sSrIdCache();
2620 }
2621 
2622 const QHash<long, QgsCoordinateReferenceSystem> &QgsCoordinateReferenceSystem::srsIdCache()
2623 {
2624  return *sSrsIdCache();
2625 }
2626 
2628 {
2629  if ( isGeographic() )
2630  {
2631  return *this;
2632  }
2633 
2634  if ( PJ *obj = d->threadLocalProjObject() )
2635  {
2636  PJ_CONTEXT *pjContext = QgsProjContext::get();
2637  QgsProjUtils::proj_pj_unique_ptr geoCrs( proj_crs_get_geodetic_crs( pjContext, obj ) );
2638  if ( !geoCrs )
2640 
2641  if ( !testIsGeographic( geoCrs.get() ) )
2643 
2644  QgsProjUtils::proj_pj_unique_ptr normalized( proj_normalize_for_visualization( pjContext, geoCrs.get() ) );
2645  if ( !normalized )
2647 
2648  return QgsCoordinateReferenceSystem::fromProjObject( normalized.get() );
2649  }
2650  else
2651  {
2653  }
2654 }
2655 
2657 {
2658  if ( isGeographic() )
2659  {
2660  return d->mAuthId;
2661  }
2662  else if ( PJ *obj = d->threadLocalProjObject() )
2663  {
2664  QgsProjUtils::proj_pj_unique_ptr geoCrs( proj_crs_get_geodetic_crs( QgsProjContext::get(), obj ) );
2665  return geoCrs ? QStringLiteral( "%1:%2" ).arg( proj_get_id_auth_name( geoCrs.get(), 0 ), proj_get_id_code( geoCrs.get(), 0 ) ) : QString();
2666  }
2667  else
2668  {
2669  return QString();
2670  }
2671 }
2672 
2674 {
2675  return d->threadLocalProjObject();
2676 }
2677 
2679 {
2681  crs.createFromProjObject( object );
2682  return crs;
2683 }
2684 
2686 {
2687  d.detach();
2688  d->mIsValid = false;
2689  d->mProj4.clear();
2690  d->mWktPreferred.clear();
2691 
2692  if ( !object )
2693  {
2694  return false;
2695  }
2696 
2697  switch ( proj_get_type( object ) )
2698  {
2699  case PJ_TYPE_GEODETIC_CRS:
2700  case PJ_TYPE_GEOCENTRIC_CRS:
2701  case PJ_TYPE_GEOGRAPHIC_CRS:
2702  case PJ_TYPE_GEOGRAPHIC_2D_CRS:
2703  case PJ_TYPE_GEOGRAPHIC_3D_CRS:
2704  case PJ_TYPE_VERTICAL_CRS:
2705  case PJ_TYPE_PROJECTED_CRS:
2706  case PJ_TYPE_COMPOUND_CRS:
2707  case PJ_TYPE_TEMPORAL_CRS:
2708  case PJ_TYPE_ENGINEERING_CRS:
2709  case PJ_TYPE_BOUND_CRS:
2710  case PJ_TYPE_OTHER_CRS:
2711  break;
2712 
2713  default:
2714  return false;
2715  }
2716 
2717  d->setPj( QgsProjUtils::crsToSingleCrs( object ) );
2718 
2719  if ( !d->hasPj() )
2720  {
2721  return d->mIsValid;
2722  }
2723  else
2724  {
2725  // maybe we can directly grab the auth name and code from the crs
2726  const QString authName( proj_get_id_auth_name( d->threadLocalProjObject(), 0 ) );
2727  const QString authCode( proj_get_id_code( d->threadLocalProjObject(), 0 ) );
2728  if ( !authName.isEmpty() && !authCode.isEmpty() && loadFromAuthCode( authName, authCode ) )
2729  {
2730  return d->mIsValid;
2731  }
2732  else
2733  {
2734  // Still a valid CRS, just not a known one
2735  d->mIsValid = true;
2736  d->mDescription = QString( proj_get_name( d->threadLocalProjObject() ) );
2737  setMapUnits();
2738  }
2739  }
2740 
2741  return d->mIsValid;
2742 }
2743 
2745 {
2746  QStringList projections;
2747  const QList<QgsCoordinateReferenceSystem> res = recentCoordinateReferenceSystems();
2748  projections.reserve( res.size() );
2749  for ( const QgsCoordinateReferenceSystem &crs : res )
2750  {
2751  projections << QString::number( crs.srsid() );
2752  }
2753  return projections;
2754 }
2755 
2757 {
2758  QList<QgsCoordinateReferenceSystem> res;
2759 
2760  // Read settings from persistent storage
2761  QgsSettings settings;
2762  QStringList projectionsProj4 = settings.value( QStringLiteral( "UI/recentProjectionsProj4" ) ).toStringList();
2763  QStringList projectionsWkt = settings.value( QStringLiteral( "UI/recentProjectionsWkt" ) ).toStringList();
2764  QStringList projectionsAuthId = settings.value( QStringLiteral( "UI/recentProjectionsAuthId" ) ).toStringList();
2765  int max = std::max( projectionsAuthId.size(), std::max( projectionsProj4.size(), projectionsWkt.size() ) );
2766  res.reserve( max );
2767  for ( int i = 0; i < max; ++i )
2768  {
2769  const QString proj = projectionsProj4.value( i );
2770  const QString wkt = projectionsWkt.value( i );
2771  const QString authid = projectionsAuthId.value( i );
2772 
2774  if ( !authid.isEmpty() )
2776  if ( !crs.isValid() && !wkt.isEmpty() )
2777  crs.createFromWkt( wkt );
2778  if ( !crs.isValid() && !proj.isEmpty() )
2779  crs.createFromProj( wkt );
2780 
2781  if ( crs.isValid() )
2782  res << crs;
2783  }
2784  return res;
2785 }
2786 
2788 {
2789  // we only want saved and standard CRSes in the recent list
2790  if ( crs.srsid() == 0 || !crs.isValid() )
2791  return;
2792 
2793  QList<QgsCoordinateReferenceSystem> recent = recentCoordinateReferenceSystems();
2794  recent.removeAll( crs );
2795  recent.insert( 0, crs );
2796 
2797  // trim to max 30 items
2798  recent = recent.mid( 0, 30 );
2799  QStringList authids;
2800  authids.reserve( recent.size() );
2801  QStringList proj;
2802  proj.reserve( recent.size() );
2803  QStringList wkt;
2804  wkt.reserve( recent.size() );
2805  for ( const QgsCoordinateReferenceSystem &c : std::as_const( recent ) )
2806  {
2807  authids << c.authid();
2808  proj << c.toProj();
2809  wkt << c.toWkt( WKT_PREFERRED );
2810  }
2811 
2812  QgsSettings settings;
2813  settings.setValue( QStringLiteral( "UI/recentProjectionsAuthId" ), authids );
2814  settings.setValue( QStringLiteral( "UI/recentProjectionsWkt" ), wkt );
2815  settings.setValue( QStringLiteral( "UI/recentProjectionsProj4" ), proj );
2816 }
2817 
2819 {
2820  sSrIdCacheLock()->lockForWrite();
2821  if ( !sDisableSrIdCache )
2822  {
2823  if ( disableCache )
2824  sDisableSrIdCache = true;
2825  sSrIdCache()->clear();
2826  }
2827  sSrIdCacheLock()->unlock();
2828 
2829  sOgcLock()->lockForWrite();
2830  if ( !sDisableOgcCache )
2831  {
2832  if ( disableCache )
2833  sDisableOgcCache = true;
2834  sOgcCache()->clear();
2835  }
2836  sOgcLock()->unlock();
2837 
2838  sProj4CacheLock()->lockForWrite();
2839  if ( !sDisableProjCache )
2840  {
2841  if ( disableCache )
2842  sDisableProjCache = true;
2843  sProj4Cache()->clear();
2844  }
2845  sProj4CacheLock()->unlock();
2846 
2847  sCRSWktLock()->lockForWrite();
2848  if ( !sDisableWktCache )
2849  {
2850  if ( disableCache )
2851  sDisableWktCache = true;
2852  sWktCache()->clear();
2853  }
2854  sCRSWktLock()->unlock();
2855 
2856  sCRSSrsIdLock()->lockForWrite();
2857  if ( !sDisableSrsIdCache )
2858  {
2859  if ( disableCache )
2860  sDisableSrsIdCache = true;
2861  sSrsIdCache()->clear();
2862  }
2863  sCRSSrsIdLock()->unlock();
2864 
2865  sCrsStringLock()->lockForWrite();
2866  if ( !sDisableStringCache )
2867  {
2868  if ( disableCache )
2869  sDisableStringCache = true;
2870  sStringCache()->clear();
2871  }
2872  sCrsStringLock()->unlock();
2873 }
2874 
2875 // invalid < regular < user
2877 {
2878  if ( c1.d == c2.d )
2879  return false;
2880 
2881  if ( !c1.d->mIsValid && !c2.d->mIsValid )
2882  return false;
2883 
2884  if ( !c1.d->mIsValid && c2.d->mIsValid )
2885  return false;
2886 
2887  if ( c1.d->mIsValid && !c2.d->mIsValid )
2888  return true;
2889 
2890  const bool c1IsUser = c1.d->mSrsId >= USER_CRS_START_ID;
2891  const bool c2IsUser = c2.d->mSrsId >= USER_CRS_START_ID;
2892 
2893  if ( c1IsUser && !c2IsUser )
2894  return true;
2895 
2896  if ( !c1IsUser && c2IsUser )
2897  return false;
2898 
2899  if ( !c1IsUser && !c2IsUser )
2900  {
2901  if ( c1.d->mAuthId != c2.d->mAuthId )
2902  return c1.d->mAuthId > c2.d->mAuthId;
2903  }
2904  else
2905  {
2906  const QString wkt1 = c1.toWkt( QgsCoordinateReferenceSystem::WKT_PREFERRED );
2907  const QString wkt2 = c2.toWkt( QgsCoordinateReferenceSystem::WKT_PREFERRED );
2908  if ( wkt1 != wkt2 )
2909  return wkt1 > wkt2;
2910  }
2911 
2912  if ( c1.d->mCoordinateEpoch == c2.d->mCoordinateEpoch )
2913  return false;
2914 
2915  if ( std::isnan( c1.d->mCoordinateEpoch ) && std::isnan( c2.d->mCoordinateEpoch ) )
2916  return false;
2917 
2918  if ( std::isnan( c1.d->mCoordinateEpoch ) && !std::isnan( c2.d->mCoordinateEpoch ) )
2919  return false;
2920 
2921  if ( !std::isnan( c1.d->mCoordinateEpoch ) && std::isnan( c2.d->mCoordinateEpoch ) )
2922  return true;
2923 
2924  return c1.d->mCoordinateEpoch > c2.d->mCoordinateEpoch;
2925 }
2926 
2928 {
2929  if ( c1.d == c2.d )
2930  return false;
2931 
2932  if ( !c1.d->mIsValid && !c2.d->mIsValid )
2933  return false;
2934 
2935  if ( c1.d->mIsValid && !c2.d->mIsValid )
2936  return false;
2937 
2938  if ( !c1.d->mIsValid && c2.d->mIsValid )
2939  return true;
2940 
2941  const bool c1IsUser = c1.d->mSrsId >= USER_CRS_START_ID;
2942  const bool c2IsUser = c2.d->mSrsId >= USER_CRS_START_ID;
2943 
2944  if ( !c1IsUser && c2IsUser )
2945  return true;
2946 
2947  if ( c1IsUser && !c2IsUser )
2948  return false;
2949 
2950  if ( !c1IsUser && !c2IsUser )
2951  {
2952  if ( c1.d->mAuthId != c2.d->mAuthId )
2953  return c1.d->mAuthId < c2.d->mAuthId;
2954  }
2955  else
2956  {
2957  const QString wkt1 = c1.toWkt( QgsCoordinateReferenceSystem::WKT_PREFERRED );
2958  const QString wkt2 = c2.toWkt( QgsCoordinateReferenceSystem::WKT_PREFERRED );
2959  if ( wkt1 != wkt2 )
2960  return wkt1 < wkt2;
2961  }
2962 
2963  if ( c1.d->mCoordinateEpoch == c2.d->mCoordinateEpoch )
2964  return false;
2965 
2966  if ( std::isnan( c1.d->mCoordinateEpoch ) && std::isnan( c2.d->mCoordinateEpoch ) )
2967  return false;
2968 
2969  if ( !std::isnan( c1.d->mCoordinateEpoch ) && std::isnan( c2.d->mCoordinateEpoch ) )
2970  return false;
2971 
2972  if ( std::isnan( c1.d->mCoordinateEpoch ) && !std::isnan( c2.d->mCoordinateEpoch ) )
2973  return true;
2974 
2975  return c1.d->mCoordinateEpoch < c2.d->mCoordinateEpoch;
2976 }
2977 
2979 {
2980  return !( c1 < c2 );
2981 }
2983 {
2984  return !( c1 > c2 );
2985 }
CrsDefinitionFormat
CRS definition formats.
Definition: qgis.h:1318
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.
QString validationHint()
Gets user hint for validation.
bool createFromSrsId(long srsId)
Sets this CRS by lookup of internal QGIS CRS ID in the CRS database.
Q_DECL_DEPRECATED bool createFromId(long id, CrsType type=PostgisCrsId)
Sets this CRS by lookup of the given ID in the CRS database.
static 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 toWkt(WktVariant variant=WKT1_GDAL, bool multiline=false, int indentationWidth=4) const
Returns a WKT representation of this CRS.
QgsProjOperation operation() const
Returns information about the PROJ operation associated with the coordinate reference system,...
Q_GADGET QgsUnitTypes::DistanceUnit mapUnits
long srsid() const
Returns the internal CRS ID, if available.
bool hasAxisInverted() const
Returns whether axis is inverted (e.g., for WMS 1.3) for the CRS.
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:2065
QString qgsDoubleToString(double a, int precision=17)
Returns a string representation of a double.
Definition: qgis.h:1530
#define Q_NOWARN_DEPRECATED_PUSH
Definition: qgis.h:2064
bool qgsNanCompatibleEquals(double a, double b)
Compare two doubles, treating nan values as equal.
Definition: qgis.h:1562
const int USER_CRS_START_ID
Magick number that determines whether a projection crsid is a system (srs.db) or user (~/....
Definition: qgis.h:2013
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