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