QGIS API Documentation  3.18.1-Zürich (202f1bf7e5)
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 
44 #include <sqlite3.h>
45 #if PROJ_VERSION_MAJOR>=6
46 #include "qgsprojutils.h"
47 #include <proj.h>
48 #include <proj_experimental.h>
49 #else
50 #include <proj_api.h>
51 #endif
52 
53 //gdal and ogr includes (needed for == operator)
54 #include <ogr_srs_api.h>
55 #include <cpl_error.h>
56 #include <cpl_conv.h>
57 #include <cpl_csv.h>
58 
59 
60 #if PROJ_VERSION_MAJOR<6
62 const int LAT_PREFIX_LEN = 7;
63 #endif
64 
65 CUSTOM_CRS_VALIDATION QgsCoordinateReferenceSystem::sCustomSrsValidation = nullptr;
66 
67 typedef QHash< long, QgsCoordinateReferenceSystem > SrIdCrsCacheHash;
68 typedef QHash< QString, QgsCoordinateReferenceSystem > StringCrsCacheHash;
69 
70 Q_GLOBAL_STATIC( QReadWriteLock, sSrIdCacheLock )
71 Q_GLOBAL_STATIC( SrIdCrsCacheHash, sSrIdCache )
72 bool QgsCoordinateReferenceSystem::sDisableSrIdCache = false;
73 
74 Q_GLOBAL_STATIC( QReadWriteLock, sOgcLock )
76 bool QgsCoordinateReferenceSystem::sDisableOgcCache = false;
77 
78 Q_GLOBAL_STATIC( QReadWriteLock, sProj4CacheLock )
79 Q_GLOBAL_STATIC( StringCrsCacheHash, sProj4Cache )
80 bool QgsCoordinateReferenceSystem::sDisableProjCache = false;
81 
82 Q_GLOBAL_STATIC( QReadWriteLock, sCRSWktLock )
84 bool QgsCoordinateReferenceSystem::sDisableWktCache = false;
85 
86 Q_GLOBAL_STATIC( QReadWriteLock, sCRSSrsIdLock )
87 Q_GLOBAL_STATIC( SrIdCrsCacheHash, sSrsIdCache )
88 bool QgsCoordinateReferenceSystem::sDisableSrsIdCache = false;
89 
90 Q_GLOBAL_STATIC( QReadWriteLock, sCrsStringLock )
91 Q_GLOBAL_STATIC( StringCrsCacheHash, sStringCache )
92 bool QgsCoordinateReferenceSystem::sDisableStringCache = false;
93 
94 #if PROJ_VERSION_MAJOR>=6
95 QString getFullProjString( PJ *obj )
96 {
97  // see https://lists.osgeo.org/pipermail/proj/2019-May/008565.html, it's not sufficient to just
98  // use proj_as_proj_string
99  QgsProjUtils::proj_pj_unique_ptr boundCrs( proj_crs_create_bound_crs_to_WGS84( QgsProjContext::get(), obj, nullptr ) );
100  if ( boundCrs )
101  {
102  if ( const char *proj4src = proj_as_proj_string( QgsProjContext::get(), boundCrs.get(), PJ_PROJ_4, nullptr ) )
103  {
104  return QString( proj4src );
105  }
106  }
107 
108  return QString( proj_as_proj_string( QgsProjContext::get(), obj, PJ_PROJ_4, nullptr ) );
109 }
110 #endif
111 //--------------------------
112 
114 {
115  static QgsCoordinateReferenceSystem nullCrs = QgsCoordinateReferenceSystem( QString() );
116 
117  d = nullCrs.d;
118 }
119 
121 {
122  d = new QgsCoordinateReferenceSystemPrivate();
123  createFromString( definition );
124 }
125 
127 {
128  d = new QgsCoordinateReferenceSystemPrivate();
130  createFromId( id, type );
132 }
133 
135  : d( srs.d )
136  , mValidationHint( srs.mValidationHint )
137 {
138 }
139 
141 {
142  d = srs.d;
143  mValidationHint = srs.mValidationHint;
144  return *this;
145 }
146 
148 {
149  QList<long> results;
150  // check both standard & user defined projection databases
151  QStringList dbs = QStringList() << QgsApplication::srsDatabaseFilePath() << QgsApplication::qgisUserDatabaseFilePath();
152 
153  const auto constDbs = dbs;
154  for ( const QString &db : constDbs )
155  {
156  QFileInfo myInfo( db );
157  if ( !myInfo.exists() )
158  {
159  QgsDebugMsg( "failed : " + db + " does not exist!" );
160  continue;
161  }
162 
165 
166  //check the db is available
167  int result = openDatabase( db, database );
168  if ( result != SQLITE_OK )
169  {
170  QgsDebugMsg( "failed : " + db + " could not be opened!" );
171  continue;
172  }
173 
174  QString sql = QStringLiteral( "select srs_id from tbl_srs" );
175  int rc;
176  statement = database.prepare( sql, rc );
177  while ( true )
178  {
179  // this one is an infinitive loop, intended to fetch any row
180  int ret = statement.step();
181 
182  if ( ret == SQLITE_DONE )
183  {
184  // there are no more rows to fetch - we can stop looping
185  break;
186  }
187 
188  if ( ret == SQLITE_ROW )
189  {
190  results.append( statement.columnAsInt64( 0 ) );
191  }
192  else
193  {
194  QgsMessageLog::logMessage( QObject::tr( "SQLite error: %2\nSQL: %1" ).arg( sql, sqlite3_errmsg( database.get() ) ), QObject::tr( "SpatiaLite" ) );
195  break;
196  }
197  }
198  }
199  std::sort( results.begin(), results.end() );
200  return results;
201 }
202 
204 {
206  crs.createFromOgcWmsCrs( ogcCrs );
207  return crs;
208 }
209 
211 {
212  QgsCoordinateReferenceSystem res = fromOgcWmsCrs( "EPSG:" + QString::number( epsg ) );
213  if ( res.isValid() )
214  return res;
215 
216  // pre proj6 builds allowed use of ESRI: codes here (e.g. 54030), so we need to keep compatibility
217  res = fromOgcWmsCrs( "ESRI:" + QString::number( epsg ) );
218  if ( res.isValid() )
219  return res;
220 
222 }
223 
225 {
226  return fromProj( proj4 );
227 }
228 
230 {
232  crs.createFromProj( proj );
233  return crs;
234 }
235 
237 {
239  crs.createFromWkt( wkt );
240  return crs;
241 }
242 
244 {
246  crs.createFromSrsId( srsId );
247  return crs;
248 }
249 
251 {
252 }
253 
255 {
256  bool result = false;
257  switch ( type )
258  {
259  case InternalCrsId:
260  result = createFromSrsId( id );
261  break;
262  case PostgisCrsId:
263  result = createFromPostgisSrid( id );
264  break;
265  case EpsgCrsId:
266  result = createFromOgcWmsCrs( QStringLiteral( "EPSG:%1" ).arg( id ) );
267  break;
268  default:
269  //THIS IS BAD...THIS PART OF CODE SHOULD NEVER BE REACHED...
270  QgsDebugMsg( QStringLiteral( "Unexpected case reached!" ) );
271  };
272  return result;
273 }
274 
275 bool QgsCoordinateReferenceSystem::createFromString( const QString &definition )
276 {
277  if ( definition.isEmpty() )
278  return false;
279 
280  QgsReadWriteLocker locker( *sCrsStringLock(), QgsReadWriteLocker::Read );
281  if ( !sDisableStringCache )
282  {
283  QHash< QString, QgsCoordinateReferenceSystem >::const_iterator crsIt = sStringCache()->constFind( definition );
284  if ( crsIt != sStringCache()->constEnd() )
285  {
286  // found a match in the cache
287  *this = crsIt.value();
288  return d->mIsValid;
289  }
290  }
291  locker.unlock();
292 
293  bool result = false;
294  const thread_local QRegularExpression reCrsId( QStringLiteral( "^(epsg|esri|osgeo|ignf|zangi|iau2000|postgis|internal|user)\\:(\\w+)$" ), QRegularExpression::CaseInsensitiveOption );
295  QRegularExpressionMatch match = reCrsId.match( definition );
296  if ( match.capturedStart() == 0 )
297  {
298  QString authName = match.captured( 1 ).toLower();
299  if ( authName == QLatin1String( "epsg" ) )
300  {
301  result = createFromOgcWmsCrs( definition );
302  }
303  else if ( authName == QLatin1String( "postgis" ) )
304  {
305  const long id = match.captured( 2 ).toLong();
306  result = createFromPostgisSrid( id );
307  }
308  else if ( authName == QLatin1String( "esri" ) || authName == QLatin1String( "osgeo" ) || authName == QLatin1String( "ignf" ) || authName == QLatin1String( "zangi" ) || authName == QLatin1String( "iau2000" ) )
309  {
310  result = createFromOgcWmsCrs( definition );
311  }
312  else
313  {
314  const long id = match.captured( 2 ).toLong();
316  result = createFromId( id, InternalCrsId );
318  }
319  }
320  else
321  {
322  const thread_local QRegularExpression reCrsStr( QStringLiteral( "^(?:(wkt|proj4|proj)\\:)?(.+)$" ), QRegularExpression::CaseInsensitiveOption );
323  match = reCrsStr.match( definition );
324  if ( match.capturedStart() == 0 )
325  {
326  if ( match.captured( 1 ).startsWith( QLatin1String( "proj" ), Qt::CaseInsensitive ) )
327  {
328  result = createFromProj( match.captured( 2 ) );
329  }
330  else
331  {
332  result = createFromWkt( match.captured( 2 ) );
333  }
334  }
335  }
336 
338  if ( !sDisableStringCache )
339  sStringCache()->insert( definition, *this );
340  return result;
341 }
342 
343 bool QgsCoordinateReferenceSystem::createFromUserInput( const QString &definition )
344 {
345  if ( definition.isEmpty() )
346  return false;
347 
348  QString userWkt;
349  OGRSpatialReferenceH crs = OSRNewSpatialReference( nullptr );
350 
351 #if PROJ_VERSION_MAJOR<6
352  // make sure towgs84 parameter is loaded if using an ESRI definition and gdal >= 1.9
353  if ( definition.startsWith( QLatin1String( "ESRI::" ) ) )
354  {
356  setupESRIWktFix();
358  }
359 #endif
360 
361  if ( OSRSetFromUserInput( crs, definition.toLocal8Bit().constData() ) == OGRERR_NONE )
362  {
364  OSRDestroySpatialReference( crs );
365  }
366  //QgsDebugMsg( "definition: " + definition + " wkt = " + wkt );
367  return createFromWkt( userWkt );
368 }
369 
371 {
372  // make sure towgs84 parameter is loaded if gdal >= 1.9
373  // this requires setting GDAL_FIX_ESRI_WKT=GEOGCS (see qgis bug #5598 and gdal bug #4673)
374  const char *configOld = CPLGetConfigOption( "GDAL_FIX_ESRI_WKT", "" );
375  const char *configNew = "GEOGCS";
376  // only set if it was not set, to let user change the value if needed
377  if ( strcmp( configOld, "" ) == 0 )
378  {
379  CPLSetConfigOption( "GDAL_FIX_ESRI_WKT", configNew );
380  if ( strcmp( configNew, CPLGetConfigOption( "GDAL_FIX_ESRI_WKT", "" ) ) != 0 )
381  QgsLogger::warning( QStringLiteral( "GDAL_FIX_ESRI_WKT could not be set to %1 : %2" )
382  .arg( configNew, CPLGetConfigOption( "GDAL_FIX_ESRI_WKT", "" ) ) );
383  QgsDebugMsgLevel( QStringLiteral( "set GDAL_FIX_ESRI_WKT : %1" ).arg( configNew ), 4 );
384  }
385  else
386  {
387  QgsDebugMsgLevel( QStringLiteral( "GDAL_FIX_ESRI_WKT was already set : %1" ).arg( configNew ), 4 );
388  }
389 }
390 
392 {
393  if ( crs.isEmpty() )
394  return false;
395 
396  QgsReadWriteLocker locker( *sOgcLock(), QgsReadWriteLocker::Read );
397  if ( !sDisableOgcCache )
398  {
399  QHash< QString, QgsCoordinateReferenceSystem >::const_iterator crsIt = sOgcCache()->constFind( crs );
400  if ( crsIt != sOgcCache()->constEnd() )
401  {
402  // found a match in the cache
403  *this = crsIt.value();
404  return d->mIsValid;
405  }
406  }
407  locker.unlock();
408 
409  QString wmsCrs = crs;
410 
411  thread_local const QRegExp re_uri( QStringLiteral( "http://www\\.opengis\\.net/def/crs/([^/]+).+/([^/]+)" ), Qt::CaseInsensitive );
412  thread_local const QRegExp re_urn( QStringLiteral( "urn:ogc:def:crs:([^:]+).+([^:]+)" ), Qt::CaseInsensitive );
413  if ( re_uri.exactMatch( wmsCrs ) )
414  {
415  wmsCrs = re_uri.cap( 1 ) + ':' + re_uri.cap( 2 );
416  }
417  else if ( re_urn.exactMatch( wmsCrs ) )
418  {
419  wmsCrs = re_urn.cap( 1 ) + ':' + re_urn.cap( 2 );
420  }
421  else
422  {
423  thread_local const QRegExp re_urn_custom( QStringLiteral( "(user|custom|qgis):(\\d+)" ), Qt::CaseInsensitive );
424  if ( re_urn_custom.exactMatch( wmsCrs ) && createFromSrsId( re_urn_custom.cap( 2 ).toInt() ) )
425  {
427  if ( !sDisableOgcCache )
428  sOgcCache()->insert( crs, *this );
429  return d->mIsValid;
430  }
431  }
432 
433 #if PROJ_VERSION_MAJOR>=6
434  // first chance for proj 6 - scan through legacy systems and try to use authid directly
435  const QString legacyKey = wmsCrs.toLower();
436  for ( auto it = sAuthIdToQgisSrsIdMap.constBegin(); it != sAuthIdToQgisSrsIdMap.constEnd(); ++it )
437  {
438  if ( it.key().compare( legacyKey, Qt::CaseInsensitive ) == 0 )
439  {
440  const QStringList parts = it.key().split( ':' );
441  const QString auth = parts.at( 0 );
442  const QString code = parts.at( 1 );
443  if ( loadFromAuthCode( auth, code ) )
444  {
446  if ( !sDisableOgcCache )
447  sOgcCache()->insert( crs, *this );
448  return d->mIsValid;
449  }
450  }
451  }
452 #endif
453 
454  if ( loadFromDatabase( QgsApplication::srsDatabaseFilePath(), QStringLiteral( "lower(auth_name||':'||auth_id)" ), wmsCrs.toLower() ) )
455  {
457  if ( !sDisableOgcCache )
458  sOgcCache()->insert( crs, *this );
459  return d->mIsValid;
460  }
461 
462  // NAD27
463  if ( wmsCrs.compare( QLatin1String( "CRS:27" ), Qt::CaseInsensitive ) == 0 ||
464  wmsCrs.compare( QLatin1String( "OGC:CRS27" ), Qt::CaseInsensitive ) == 0 )
465  {
466  // TODO: verify same axis orientation
467  return createFromOgcWmsCrs( QStringLiteral( "EPSG:4267" ) );
468  }
469 
470  // NAD83
471  if ( wmsCrs.compare( QLatin1String( "CRS:83" ), Qt::CaseInsensitive ) == 0 ||
472  wmsCrs.compare( QLatin1String( "OGC:CRS83" ), Qt::CaseInsensitive ) == 0 )
473  {
474  // TODO: verify same axis orientation
475  return createFromOgcWmsCrs( QStringLiteral( "EPSG:4269" ) );
476  }
477 
478  // WGS84
479  if ( wmsCrs.compare( QLatin1String( "CRS:84" ), Qt::CaseInsensitive ) == 0 ||
480  wmsCrs.compare( QLatin1String( "OGC:CRS84" ), Qt::CaseInsensitive ) == 0 )
481  {
482  if ( loadFromDatabase( QgsApplication::srsDatabaseFilePath(), QStringLiteral( "lower(auth_name||':'||auth_id)" ), QStringLiteral( "epsg:4326" ) ) )
483  {
484  d->mAxisInverted = false;
485  d->mAxisInvertedDirty = false;
486  }
487 
489  if ( !sDisableOgcCache )
490  sOgcCache()->insert( crs, *this );
491 
492  return d->mIsValid;
493  }
494 
496  if ( !sDisableOgcCache )
497  sOgcCache()->insert( crs, QgsCoordinateReferenceSystem() );
498  return d->mIsValid;
499 }
500 
501 // Misc helper functions -----------------------
502 
503 
505 {
506  if ( d->mIsValid || !sCustomSrsValidation )
507  return;
508 
509  // try to validate using custom validation routines
510  if ( sCustomSrsValidation )
511  sCustomSrsValidation( *this );
512 }
513 
515 {
516  return createFromPostgisSrid( id );
517 }
518 
519 bool QgsCoordinateReferenceSystem::createFromPostgisSrid( const long id )
520 {
521  QgsReadWriteLocker locker( *sSrIdCacheLock(), QgsReadWriteLocker::Read );
522  if ( !sDisableSrIdCache )
523  {
524  QHash< long, QgsCoordinateReferenceSystem >::const_iterator crsIt = sSrIdCache()->constFind( id );
525  if ( crsIt != sSrIdCache()->constEnd() )
526  {
527  // found a match in the cache
528  *this = crsIt.value();
529  return d->mIsValid;
530  }
531  }
532  locker.unlock();
533 
534 #if PROJ_VERSION_MAJOR>=6
535  // first chance for proj 6 - scan through legacy systems and try to use authid directly
536  for ( auto it = sAuthIdToQgisSrsIdMap.constBegin(); it != sAuthIdToQgisSrsIdMap.constEnd(); ++it )
537  {
538  if ( it.value().endsWith( QStringLiteral( ",%1" ).arg( id ) ) )
539  {
540  const QStringList parts = it.key().split( ':' );
541  const QString auth = parts.at( 0 );
542  const QString code = parts.at( 1 );
543  if ( loadFromAuthCode( auth, code ) )
544  {
545  locker.changeMode( QgsReadWriteLocker::Write );
546  if ( !sDisableSrIdCache )
547  sSrIdCache()->insert( id, *this );
548 
549  return d->mIsValid;
550  }
551  }
552  }
553 #endif
554 
555  bool result = loadFromDatabase( QgsApplication::srsDatabaseFilePath(), QStringLiteral( "srid" ), QString::number( id ) );
556 
557  locker.changeMode( QgsReadWriteLocker::Write );
558  if ( !sDisableSrIdCache )
559  sSrIdCache()->insert( id, *this );
560 
561  return result;
562 }
563 
565 {
566  QgsReadWriteLocker locker( *sCRSSrsIdLock(), QgsReadWriteLocker::Read );
567  if ( !sDisableSrsIdCache )
568  {
569  QHash< long, QgsCoordinateReferenceSystem >::const_iterator crsIt = sSrsIdCache()->constFind( id );
570  if ( crsIt != sSrsIdCache()->constEnd() )
571  {
572  // found a match in the cache
573  *this = crsIt.value();
574  return d->mIsValid;
575  }
576  }
577  locker.unlock();
578 
579 #if PROJ_VERSION_MAJOR>=6
580  // first chance for proj 6 - scan through legacy systems and try to use authid directly
581  for ( auto it = sAuthIdToQgisSrsIdMap.constBegin(); it != sAuthIdToQgisSrsIdMap.constEnd(); ++it )
582  {
583  if ( it.value().startsWith( QString::number( id ) + ',' ) )
584  {
585  const QStringList parts = it.key().split( ':' );
586  const QString auth = parts.at( 0 );
587  const QString code = parts.at( 1 );
588  if ( loadFromAuthCode( auth, code ) )
589  {
591  if ( !sDisableSrsIdCache )
592  sSrsIdCache()->insert( id, *this );
593  return d->mIsValid;
594  }
595  }
596  }
597 #endif
598 
599  bool result = loadFromDatabase( id < USER_CRS_START_ID ? QgsApplication::srsDatabaseFilePath() :
601  QStringLiteral( "srs_id" ), QString::number( id ) );
602 
604  if ( !sDisableSrsIdCache )
605  sSrsIdCache()->insert( id, *this );
606  return result;
607 }
608 
609 bool QgsCoordinateReferenceSystem::loadFromDatabase( const QString &db, const QString &expression, const QString &value )
610 {
611  d.detach();
612 
613  QgsDebugMsgLevel( "load CRS from " + db + " where " + expression + " is " + value, 3 );
614  d->mIsValid = false;
615  d->mWktPreferred.clear();
616 
617  QFileInfo myInfo( db );
618  if ( !myInfo.exists() )
619  {
620  QgsDebugMsg( "failed : " + db + " does not exist!" );
621  return d->mIsValid;
622  }
623 
626  int myResult;
627  //check the db is available
628  myResult = openDatabase( db, database );
629  if ( myResult != SQLITE_OK )
630  {
631  return d->mIsValid;
632  }
633 
634  /*
635  srs_id INTEGER PRIMARY KEY,
636  description text NOT NULL,
637  projection_acronym text NOT NULL,
638  ellipsoid_acronym NOT NULL,
639  parameters text NOT NULL,
640  srid integer NOT NULL,
641  auth_name varchar NOT NULL,
642  auth_id integer NOT NULL,
643  is_geo integer NOT NULL);
644  */
645 
646  QString mySql = "select srs_id,description,projection_acronym,"
647  "ellipsoid_acronym,parameters,srid,auth_name||':'||auth_id,is_geo,wkt "
648  "from tbl_srs where " + expression + '=' + QgsSqliteUtils::quotedString( value ) + " order by deprecated";
649  statement = database.prepare( mySql, myResult );
650  QString wkt;
651  // XXX Need to free memory from the error msg if one is set
652  if ( myResult == SQLITE_OK && statement.step() == SQLITE_ROW )
653  {
654  d->mSrsId = statement.columnAsText( 0 ).toLong();
655  d->mDescription = statement.columnAsText( 1 );
656  d->mProjectionAcronym = statement.columnAsText( 2 );
657 #if PROJ_VERSION_MAJOR>=6
658  d->mEllipsoidAcronym.clear();
659 #else
660  d->mEllipsoidAcronym = statement.columnAsText( 3 );
661 #endif
662  d->mProj4 = statement.columnAsText( 4 );
663  d->mWktPreferred.clear();
664  d->mSRID = statement.columnAsText( 5 ).toLong();
665  d->mAuthId = statement.columnAsText( 6 );
666  d->mIsGeographic = statement.columnAsText( 7 ).toInt() != 0;
667  wkt = statement.columnAsText( 8 );
668  d->mAxisInvertedDirty = true;
669 
670  if ( d->mSrsId >= USER_CRS_START_ID && ( d->mAuthId.isEmpty() || d->mAuthId == QChar( ':' ) ) )
671  {
672  d->mAuthId = QStringLiteral( "USER:%1" ).arg( d->mSrsId );
673  }
674  else if ( !d->mAuthId.startsWith( QLatin1String( "USER:" ), Qt::CaseInsensitive ) )
675  {
676 #if PROJ_VERSION_MAJOR>=6
677  QStringList parts = d->mAuthId.split( ':' );
678  QString auth = parts.at( 0 );
679  QString code = parts.at( 1 );
680 
681  {
682  QgsProjUtils::proj_pj_unique_ptr crs( proj_create_from_database( QgsProjContext::get(), auth.toLatin1(), code.toLatin1(), PJ_CATEGORY_CRS, false, nullptr ) );
683  d->setPj( QgsProjUtils::crsToSingleCrs( crs.get() ) );
684  }
685 
686  d->mIsValid = d->hasPj();
687 #else
688  OSRDestroySpatialReference( d->mCRS );
689  d->mCRS = OSRNewSpatialReference( nullptr );
690  d->mIsValid = OSRSetFromUserInput( d->mCRS, d->mAuthId.toLower().toLatin1() ) == OGRERR_NONE;
691 #endif
692  setMapUnits();
693  }
694 
695  if ( !d->mIsValid )
696  {
697  if ( !wkt.isEmpty() )
698  {
699  setWktString( wkt, false );
700  // set WKT string resets the description to that description embedded in the WKT, so manually overwrite this back to the
701  // value from the user DB
702  d->mDescription = statement.columnAsText( 1 );
703  }
704  else
705  setProjString( d->mProj4 );
706  }
707  }
708  else
709  {
710  QgsDebugMsgLevel( "failed : " + mySql, 4 );
711  }
712  return d->mIsValid;
713 }
714 
715 #if PROJ_VERSION_MAJOR>=6
716 void QgsCoordinateReferenceSystem::removeFromCacheObjectsBelongingToCurrentThread( PJ_CONTEXT *pj_context )
717 {
718  // Not completely sure about object order destruction after main() has
719  // exited. So it is safer to check sDisableCache before using sCacheLock
720  // in case sCacheLock would have been destroyed before the current TLS
721  // QgsProjContext object that has called us...
722 
723  if ( !sDisableSrIdCache )
724  {
725  QgsReadWriteLocker locker( *sSrIdCacheLock(), QgsReadWriteLocker::Write );
726  if ( !sDisableSrIdCache )
727  {
728  for ( auto it = sSrIdCache()->begin(); it != sSrIdCache()->end(); )
729  {
730  auto &v = it.value();
731  if ( v.d->removeObjectsBelongingToCurrentThread( pj_context ) )
732  it = sSrIdCache()->erase( it );
733  else
734  ++it;
735  }
736  }
737  }
738  if ( !sDisableOgcCache )
739  {
740  QgsReadWriteLocker locker( *sOgcLock(), QgsReadWriteLocker::Write );
741  if ( !sDisableOgcCache )
742  {
743  for ( auto it = sOgcCache()->begin(); it != sOgcCache()->end(); )
744  {
745  auto &v = it.value();
746  if ( v.d->removeObjectsBelongingToCurrentThread( pj_context ) )
747  it = sOgcCache()->erase( it );
748  else
749  ++it;
750  }
751  }
752  }
753  if ( !sDisableProjCache )
754  {
755  QgsReadWriteLocker locker( *sProj4CacheLock(), QgsReadWriteLocker::Write );
756  if ( !sDisableProjCache )
757  {
758  for ( auto it = sProj4Cache()->begin(); it != sProj4Cache()->end(); )
759  {
760  auto &v = it.value();
761  if ( v.d->removeObjectsBelongingToCurrentThread( pj_context ) )
762  it = sProj4Cache()->erase( it );
763  else
764  ++it;
765  }
766  }
767  }
768  if ( !sDisableWktCache )
769  {
770  QgsReadWriteLocker locker( *sCRSWktLock(), QgsReadWriteLocker::Write );
771  if ( !sDisableWktCache )
772  {
773  for ( auto it = sWktCache()->begin(); it != sWktCache()->end(); )
774  {
775  auto &v = it.value();
776  if ( v.d->removeObjectsBelongingToCurrentThread( pj_context ) )
777  it = sWktCache()->erase( it );
778  else
779  ++it;
780  }
781  }
782  }
783  if ( !sDisableSrsIdCache )
784  {
785  QgsReadWriteLocker locker( *sCRSSrsIdLock(), QgsReadWriteLocker::Write );
786  if ( !sDisableSrsIdCache )
787  {
788  for ( auto it = sSrsIdCache()->begin(); it != sSrsIdCache()->end(); )
789  {
790  auto &v = it.value();
791  if ( v.d->removeObjectsBelongingToCurrentThread( pj_context ) )
792  it = sSrsIdCache()->erase( it );
793  else
794  ++it;
795  }
796  }
797  }
798  if ( !sDisableStringCache )
799  {
800  QgsReadWriteLocker locker( *sCrsStringLock(), QgsReadWriteLocker::Write );
801  if ( !sDisableStringCache )
802  {
803  for ( auto it = sStringCache()->begin(); it != sStringCache()->end(); )
804  {
805  auto &v = it.value();
806  if ( v.d->removeObjectsBelongingToCurrentThread( pj_context ) )
807  it = sStringCache()->erase( it );
808  else
809  ++it;
810  }
811  }
812  }
813 }
814 #endif
815 
817 {
818  if ( d->mAxisInvertedDirty )
819  {
820 #if PROJ_VERSION_MAJOR>=6
821  d->mAxisInverted = QgsProjUtils::axisOrderIsSwapped( d->threadLocalProjObject() );
822 #else
823  OGRAxisOrientation orientation;
824  OSRGetAxis( d->mCRS, OSRIsGeographic( d->mCRS ) ? "GEOGCS" : "PROJCS", 0, &orientation );
825 
826  // If axis orientation is unknown, try again with OSRImportFromEPSGA for EPSG crs
827  if ( orientation == OAO_Other && d->mAuthId.startsWith( QLatin1String( "EPSG:" ), Qt::CaseInsensitive ) )
828  {
829  OGRSpatialReferenceH crs = OSRNewSpatialReference( nullptr );
830 
831  if ( OSRImportFromEPSGA( crs, d->mAuthId.midRef( 5 ).toInt() ) == OGRERR_NONE )
832  {
833  OSRGetAxis( crs, OSRIsGeographic( crs ) ? "GEOGCS" : "PROJCS", 0, &orientation );
834  }
835 
836  OSRDestroySpatialReference( crs );
837  }
838 
839  d->mAxisInverted = orientation == OAO_North;
840 #endif
841  d->mAxisInvertedDirty = false;
842  }
843 
844  return d->mAxisInverted;
845 }
846 
848 {
849  return createFromWktInternal( wkt, QString() );
850 }
851 
852 bool QgsCoordinateReferenceSystem::createFromWktInternal( const QString &wkt, const QString &description )
853 {
854  if ( wkt.isEmpty() )
855  return false;
856 
857  d.detach();
858 
859  QgsReadWriteLocker locker( *sCRSWktLock(), QgsReadWriteLocker::Read );
860  if ( !sDisableWktCache )
861  {
862  QHash< QString, QgsCoordinateReferenceSystem >::const_iterator crsIt = sWktCache()->constFind( wkt );
863  if ( crsIt != sWktCache()->constEnd() )
864  {
865  // found a match in the cache
866  *this = crsIt.value();
867 
868  if ( !description.isEmpty() && d->mDescription.isEmpty() )
869  {
870  // now we have a name for a previously unknown CRS! Update the cached CRS accordingly, so that we use the name from now on...
871  d->mDescription = description;
872  locker.changeMode( QgsReadWriteLocker::Write );
873  sWktCache()->insert( wkt, *this );
874  }
875  return d->mIsValid;
876  }
877  }
878  locker.unlock();
879 
880  d->mIsValid = false;
881  d->mProj4.clear();
882  d->mWktPreferred.clear();
883  if ( wkt.isEmpty() )
884  {
885  QgsDebugMsgLevel( QStringLiteral( "theWkt is uninitialized, operation failed" ), 4 );
886  return d->mIsValid;
887  }
888 
889  // try to match against user crs
890  QgsCoordinateReferenceSystem::RecordMap record = getRecord( "select * from tbl_srs where wkt=" + QgsSqliteUtils::quotedString( wkt ) + " order by deprecated" );
891  if ( !record.empty() )
892  {
893  long srsId = record[QStringLiteral( "srs_id" )].toLong();
894  if ( srsId > 0 )
895  {
896  createFromSrsId( srsId );
897  }
898  }
899  else
900  {
901  setWktString( wkt );
902  if ( !description.isEmpty() )
903  {
904  d->mDescription = description;
905  }
906  if ( d->mSrsId == 0 )
907  {
908 #if PROJ_VERSION_MAJOR>=6
909  // lastly, try a tolerant match of the created proj object against all user CRSes (allowing differences in parameter order during the comparison)
910  long id = matchToUserCrs();
911  if ( id >= USER_CRS_START_ID )
912  {
913  createFromSrsId( id );
914  }
915 #endif
916  }
917  }
918 
919  locker.changeMode( QgsReadWriteLocker::Write );
920  if ( !sDisableWktCache )
921  sWktCache()->insert( wkt, *this );
922 
923  return d->mIsValid;
924  //setMapunits will be called by createfromproj above
925 }
926 
928 {
929  return d->mIsValid;
930 }
931 
932 bool QgsCoordinateReferenceSystem::createFromProj4( const QString &proj4String )
933 {
934  return createFromProj( proj4String );
935 }
936 
937 bool QgsCoordinateReferenceSystem::createFromProj( const QString &projString, const bool identify )
938 {
939  if ( projString.isEmpty() )
940  return false;
941 
942  d.detach();
943 
944  if ( projString.trimmed().isEmpty() )
945  {
946  d->mIsValid = false;
947  d->mProj4.clear();
948  d->mWktPreferred.clear();
949  return false;
950  }
951 
952  QgsReadWriteLocker locker( *sProj4CacheLock(), QgsReadWriteLocker::Read );
953  if ( !sDisableProjCache )
954  {
955  QHash< QString, QgsCoordinateReferenceSystem >::const_iterator crsIt = sProj4Cache()->constFind( projString );
956  if ( crsIt != sProj4Cache()->constEnd() )
957  {
958  // found a match in the cache
959  *this = crsIt.value();
960  return d->mIsValid;
961  }
962  }
963  locker.unlock();
964 
965  //
966  // Examples:
967  // +proj=tmerc +lat_0=0 +lon_0=-62 +k=0.999500 +x_0=400000 +y_0=0
968  // +ellps=clrk80 +towgs84=-255,-15,71,0,0,0,0 +units=m +no_defs
969  //
970  // +proj=lcc +lat_1=46.8 +lat_0=46.8 +lon_0=2.337229166666664 +k_0=0.99987742
971  // +x_0=600000 +y_0=2200000 +a=6378249.2 +b=6356515.000000472 +units=m +no_defs
972  //
973  QString myProj4String = projString.trimmed();
974  myProj4String.remove( QStringLiteral( "+type=crs" ) );
975  myProj4String = myProj4String.trimmed();
976 
977  d->mIsValid = false;
978  d->mWktPreferred.clear();
979 #if PROJ_VERSION_MAJOR>=6
980  if ( identify )
981  {
982  // first, try to use proj to do this for us...
983  const QString projCrsString = myProj4String + ( myProj4String.contains( QStringLiteral( "+type=crs" ) ) ? QString() : QStringLiteral( " +type=crs" ) );
984  QgsProjUtils::proj_pj_unique_ptr crs( proj_create( QgsProjContext::get(), projCrsString.toLatin1().constData() ) );
985  if ( crs )
986  {
987  QString authName;
988  QString authCode;
989  if ( QgsProjUtils::identifyCrs( crs.get(), authName, authCode, QgsProjUtils::FlagMatchBoundCrsToUnderlyingSourceCrs ) )
990  {
991  const QString authid = QStringLiteral( "%1:%2" ).arg( authName, authCode );
992  if ( createFromOgcWmsCrs( authid ) )
993  {
995  if ( !sDisableProjCache )
996  sProj4Cache()->insert( projString, *this );
997  return d->mIsValid;
998  }
999  }
1000  }
1001 
1002  // try a direct match against user crses
1003  QgsCoordinateReferenceSystem::RecordMap myRecord = getRecord( "select * from tbl_srs where parameters=" + QgsSqliteUtils::quotedString( myProj4String ) + " order by deprecated" );
1004  long id = 0;
1005  if ( !myRecord.empty() )
1006  {
1007  id = myRecord[QStringLiteral( "srs_id" )].toLong();
1008  if ( id >= USER_CRS_START_ID )
1009  {
1010  createFromSrsId( id );
1011  }
1012  }
1013  if ( id < USER_CRS_START_ID )
1014  {
1015  // no direct matches, so go ahead and create a new proj object based on the proj string alone.
1016  setProjString( myProj4String );
1017 
1018  // lastly, try a tolerant match of the created proj object against all user CRSes (allowing differences in parameter order during the comparison)
1019  id = matchToUserCrs();
1020  if ( id >= USER_CRS_START_ID )
1021  {
1022  createFromSrsId( id );
1023  }
1024  }
1025  }
1026  else
1027  {
1028  setProjString( myProj4String );
1029  }
1030 
1031 #else
1032  Q_UNUSED( identify )
1033 
1034  QRegExp projRegExp( QStringLiteral( "\\+proj=(\\S+)" ) );
1035  int myStart = projRegExp.indexIn( myProj4String );
1036  if ( myStart == -1 )
1037  {
1039  if ( !sDisableProjCache )
1040  sProj4Cache()->insert( projString, *this );
1041 
1042  return d->mIsValid;
1043  }
1044 
1045  d->mProjectionAcronym = projRegExp.cap( 1 );
1046 
1047  QRegExp myEllipseRegExp( QStringLiteral( "\\+ellps=(\\S+)" ) );
1048  myStart = myEllipseRegExp.indexIn( myProj4String );
1049  if ( myStart == -1 )
1050  {
1051  d->mEllipsoidAcronym.clear();
1052  }
1053  else
1054  {
1055  d->mEllipsoidAcronym = myEllipseRegExp.cap( 1 );
1056  }
1057 
1058  QRegExp myAxisRegExp( QStringLiteral( "\\+a=(\\S+)" ) );
1059  myStart = myAxisRegExp.indexIn( myProj4String );
1060 
1061  long mySrsId = 0;
1062  QgsCoordinateReferenceSystem::RecordMap myRecord;
1063 
1064  /*
1065  * We try to match the proj string to and srsid using the following logic:
1066  * - perform a whole text search on proj4 string (if not null)
1067  */
1068  myRecord = getRecord( "select * from tbl_srs where parameters=" + QgsSqliteUtils::quotedString( myProj4String ) + " order by deprecated" );
1069  if ( myRecord.empty() )
1070  {
1071  // Ticket #722 - aaronr
1072  // Check if we can swap the lat_1 and lat_2 params (if they exist) to see if we match...
1073  // First we check for lat_1 and lat_2
1074  QRegExp myLat1RegExp( QStringLiteral( "\\+lat_1=\\S+" ) );
1075  QRegExp myLat2RegExp( QStringLiteral( "\\+lat_2=\\S+" ) );
1076  int myStart1 = 0;
1077  int myLength1 = 0;
1078  int myStart2 = 0;
1079  int myLength2 = 0;
1080  QString lat1Str;
1081  QString lat2Str;
1082  myStart1 = myLat1RegExp.indexIn( myProj4String, myStart1 );
1083  myStart2 = myLat2RegExp.indexIn( myProj4String, myStart2 );
1084  if ( myStart1 != -1 && myStart2 != -1 )
1085  {
1086  myLength1 = myLat1RegExp.matchedLength();
1087  myLength2 = myLat2RegExp.matchedLength();
1088  lat1Str = myProj4String.mid( myStart1 + LAT_PREFIX_LEN, myLength1 - LAT_PREFIX_LEN );
1089  lat2Str = myProj4String.mid( myStart2 + LAT_PREFIX_LEN, myLength2 - LAT_PREFIX_LEN );
1090  }
1091  // If we found the lat_1 and lat_2 we need to swap and check to see if we can find it...
1092  if ( !lat1Str.isEmpty() && !lat2Str.isEmpty() )
1093  {
1094  // Make our new string to check...
1095  QString proj4StringModified = myProj4String;
1096  // First just swap in the lat_2 value for lat_1 value
1097  proj4StringModified.replace( myStart1 + LAT_PREFIX_LEN, myLength1 - LAT_PREFIX_LEN, lat2Str );
1098  // Now we have to find the lat_2 location again since it has potentially moved...
1099  myStart2 = 0;
1100  myStart2 = myLat2RegExp.indexIn( projString, myStart2 );
1101  proj4StringModified.replace( myStart2 + LAT_PREFIX_LEN, myLength2 - LAT_PREFIX_LEN, lat1Str );
1102  QgsDebugMsgLevel( QStringLiteral( "trying proj4string match with swapped lat_1,lat_2" ), 4 );
1103  myRecord = getRecord( "select * from tbl_srs where parameters=" + QgsSqliteUtils::quotedString( proj4StringModified.trimmed() ) + " order by deprecated" );
1104  }
1105  }
1106 
1107  if ( myRecord.empty() )
1108  {
1109  // match all parameters individually:
1110  // - order of parameters doesn't matter
1111  // - found definition may have more parameters (like +towgs84 in GDAL)
1112  // - retry without datum, if no match is found (looks like +datum<>WGS84 was dropped in GDAL)
1113 
1114  QString sql = QStringLiteral( "SELECT * FROM tbl_srs WHERE " );
1115  QString delim;
1116  QString datum;
1117 
1118  // split on spaces followed by a plus sign (+) to deal
1119  // also with parameters containing spaces (e.g. +nadgrids)
1120  // make sure result is trimmed (#5598)
1121  QStringList myParams;
1122  thread_local const QRegularExpression regExp( "\\s+(?=\\+)" );
1123  {
1124  const auto constSplit = myProj4String.split( regExp, QString::SkipEmptyParts );
1125  for ( const QString &param : constSplit )
1126  {
1127  QString arg = QStringLiteral( "' '||parameters||' ' LIKE %1" ).arg( QgsSqliteUtils::quotedString( QStringLiteral( "% %1 %" ).arg( param.trimmed() ) ) );
1128  if ( param.startsWith( QLatin1String( "+datum=" ) ) )
1129  {
1130  datum = arg;
1131  }
1132  else
1133  {
1134  sql += delim + arg;
1135  delim = QStringLiteral( " AND " );
1136  myParams << param.trimmed();
1137  }
1138  }
1139  }
1140 
1141  if ( !datum.isEmpty() )
1142  {
1143  myRecord = getRecord( sql + delim + datum + " order by deprecated" );
1144  }
1145 
1146  if ( myRecord.empty() )
1147  {
1148  // datum might have disappeared in definition - retry without it
1149  myRecord = getRecord( sql + " order by deprecated" );
1150  }
1151 
1152  if ( !myRecord.empty() )
1153  {
1154  // Bugfix 8487 : test param lists are equal, except for +datum
1155  QStringList foundParams;
1156  const auto constSplit = myRecord["parameters"].split( regExp, QString::SkipEmptyParts );
1157  for ( const QString &param : constSplit )
1158  {
1159  if ( !param.startsWith( QLatin1String( "+datum=" ) ) )
1160  foundParams << param.trimmed();
1161  }
1162 
1163  myParams.sort();
1164  foundParams.sort();
1165 
1166  if ( myParams != foundParams )
1167  {
1168  myRecord.clear();
1169  }
1170  }
1171  }
1172 
1173  if ( !myRecord.empty() )
1174  {
1175  mySrsId = myRecord[QStringLiteral( "srs_id" )].toLong();
1176  if ( mySrsId > 0 )
1177  {
1178  createFromSrsId( mySrsId );
1179  }
1180  }
1181  else
1182  {
1183  // Last ditch attempt to piece together what we know of the projection to find a match...
1184  setProjString( myProj4String );
1186  mySrsId = findMatchingProj();
1188  if ( mySrsId > 0 )
1189  {
1190  createFromSrsId( mySrsId );
1191  }
1192  else
1193  {
1194  d->mIsValid = false;
1195  }
1196  }
1197 
1198  // if we failed to look up the projection in database, don't worry. we can still use it :)
1199  if ( !d->mIsValid )
1200  {
1201  QgsDebugMsgLevel( QStringLiteral( "Projection is not found in databases." ), 4 );
1202  //setProj4String will set mIsValidFlag to true if there is no issue
1203  setProjString( myProj4String );
1204  }
1205 #endif
1206 
1208  if ( !sDisableProjCache )
1209  sProj4Cache()->insert( projString, *this );
1210 
1211  return d->mIsValid;
1212 }
1213 
1214 //private method meant for internal use by this class only
1215 QgsCoordinateReferenceSystem::RecordMap QgsCoordinateReferenceSystem::getRecord( const QString &sql )
1216 {
1217  QString myDatabaseFileName;
1218  QgsCoordinateReferenceSystem::RecordMap myMap;
1219  QString myFieldName;
1220  QString myFieldValue;
1221  sqlite3_database_unique_ptr database;
1222  sqlite3_statement_unique_ptr statement;
1223  int myResult;
1224 
1225  // Get the full path name to the sqlite3 spatial reference database.
1226  myDatabaseFileName = QgsApplication::srsDatabaseFilePath();
1227  QFileInfo myInfo( myDatabaseFileName );
1228  if ( !myInfo.exists() )
1229  {
1230  QgsDebugMsg( "failed : " + myDatabaseFileName + " does not exist!" );
1231  return myMap;
1232  }
1233 
1234  //check the db is available
1235  myResult = openDatabase( myDatabaseFileName, database );
1236  if ( myResult != SQLITE_OK )
1237  {
1238  return myMap;
1239  }
1240 
1241  statement = database.prepare( sql, myResult );
1242  // XXX Need to free memory from the error msg if one is set
1243  if ( myResult == SQLITE_OK && statement.step() == SQLITE_ROW )
1244  {
1245  int myColumnCount = statement.columnCount();
1246  //loop through each column in the record adding its expression name and value to the map
1247  for ( int myColNo = 0; myColNo < myColumnCount; myColNo++ )
1248  {
1249  myFieldName = statement.columnName( myColNo );
1250  myFieldValue = statement.columnAsText( myColNo );
1251  myMap[myFieldName] = myFieldValue;
1252  }
1253  if ( statement.step() != SQLITE_DONE )
1254  {
1255  QgsDebugMsgLevel( QStringLiteral( "Multiple records found in srs.db" ), 4 );
1256  //be less fussy on proj 6 -- the db has MANY more entries!
1257 #if PROJ_VERSION_MAJOR<6
1258  myMap.clear();
1259 #endif
1260  }
1261  }
1262  else
1263  {
1264  QgsDebugMsgLevel( "failed : " + sql, 4 );
1265  }
1266 
1267  if ( myMap.empty() )
1268  {
1269  myDatabaseFileName = QgsApplication::qgisUserDatabaseFilePath();
1270  QFileInfo myFileInfo;
1271  myFileInfo.setFile( myDatabaseFileName );
1272  if ( !myFileInfo.exists() )
1273  {
1274  QgsDebugMsg( QStringLiteral( "user qgis.db not found" ) );
1275  return myMap;
1276  }
1277 
1278  //check the db is available
1279  myResult = openDatabase( myDatabaseFileName, database );
1280  if ( myResult != SQLITE_OK )
1281  {
1282  return myMap;
1283  }
1284 
1285  statement = database.prepare( sql, myResult );
1286  // XXX Need to free memory from the error msg if one is set
1287  if ( myResult == SQLITE_OK && statement.step() == SQLITE_ROW )
1288  {
1289  int myColumnCount = statement.columnCount();
1290  //loop through each column in the record adding its field name and value to the map
1291  for ( int myColNo = 0; myColNo < myColumnCount; myColNo++ )
1292  {
1293  myFieldName = statement.columnName( myColNo );
1294  myFieldValue = statement.columnAsText( myColNo );
1295  myMap[myFieldName] = myFieldValue;
1296  }
1297 
1298  if ( statement.step() != SQLITE_DONE )
1299  {
1300  QgsDebugMsgLevel( QStringLiteral( "Multiple records found in srs.db" ), 4 );
1301  myMap.clear();
1302  }
1303  }
1304  else
1305  {
1306  QgsDebugMsgLevel( "failed : " + sql, 4 );
1307  }
1308  }
1309  return myMap;
1310 }
1311 
1312 // Accessors -----------------------------------
1313 
1315 {
1316  return d->mSrsId;
1317 }
1318 
1320 {
1321  return d->mSRID;
1322 }
1323 
1325 {
1326  return d->mAuthId;
1327 }
1328 
1330 {
1331  if ( d->mDescription.isNull() )
1332  {
1333  return QString();
1334  }
1335  else
1336  {
1337  return d->mDescription;
1338  }
1339 }
1340 
1342 {
1343  if ( !authid().isEmpty() )
1344  {
1345  if ( type != ShortString && !description().isEmpty() )
1346  return QStringLiteral( "%1 - %2" ).arg( authid(), description() );
1347  return authid();
1348  }
1349  else if ( !description().isEmpty() )
1350  return description();
1351  else if ( type == ShortString )
1352  return isValid() ? QObject::tr( "Custom CRS" ) : QObject::tr( "Unknown CRS" );
1353  else if ( !toWkt( WKT_PREFERRED ).isEmpty() )
1354  return QObject::tr( "Custom CRS: %1" ).arg(
1355  type == MediumString ? ( toWkt( WKT_PREFERRED ).left( 50 ) + QString( QChar( 0x2026 ) ) )
1356  : toWkt( WKT_PREFERRED ) );
1357  else if ( !toProj().isEmpty() )
1358  return QObject::tr( "Custom CRS: %1" ).arg( type == MediumString ? ( toProj().left( 50 ) + QString( QChar( 0x2026 ) ) )
1359  : toProj() );
1360  else
1361  return QString();
1362 }
1363 
1365 {
1366  if ( d->mProjectionAcronym.isNull() )
1367  {
1368  return QString();
1369  }
1370  else
1371  {
1372  return d->mProjectionAcronym;
1373  }
1374 }
1375 
1377 {
1378  if ( d->mEllipsoidAcronym.isNull() )
1379  {
1380 #if PROJ_VERSION_MAJOR>=6
1381  if ( PJ *obj = d->threadLocalProjObject() )
1382  {
1383  QgsProjUtils::proj_pj_unique_ptr ellipsoid( proj_get_ellipsoid( QgsProjContext::get(), obj ) );
1384  if ( ellipsoid )
1385  {
1386  const QString ellipsoidAuthName( proj_get_id_auth_name( ellipsoid.get(), 0 ) );
1387  const QString ellipsoidAuthCode( proj_get_id_code( ellipsoid.get(), 0 ) );
1388  if ( !ellipsoidAuthName.isEmpty() && !ellipsoidAuthCode.isEmpty() )
1389  d->mEllipsoidAcronym = QStringLiteral( "%1:%2" ).arg( ellipsoidAuthName, ellipsoidAuthCode );
1390  else
1391  {
1392  double semiMajor, semiMinor, invFlattening;
1393  int semiMinorComputed = 0;
1394  if ( proj_ellipsoid_get_parameters( QgsProjContext::get(), ellipsoid.get(), &semiMajor, &semiMinor, &semiMinorComputed, &invFlattening ) )
1395  {
1396  d->mEllipsoidAcronym = QStringLiteral( "PARAMETER:%1:%2" ).arg( qgsDoubleToString( semiMajor ),
1397  qgsDoubleToString( semiMinor ) );
1398  }
1399  else
1400  {
1401  d->mEllipsoidAcronym.clear();
1402  }
1403  }
1404  }
1405  }
1406  return d->mEllipsoidAcronym;
1407 #else
1408  return QString();
1409 
1410 #endif
1411  }
1412  else
1413  {
1414  return d->mEllipsoidAcronym;
1415  }
1416 }
1417 
1419 {
1420  return toProj();
1421 }
1422 
1424 {
1425  if ( !d->mIsValid )
1426  return QString();
1427 
1428  if ( d->mProj4.isEmpty() )
1429  {
1430 #if PROJ_VERSION_MAJOR>=6
1431  if ( PJ *obj = d->threadLocalProjObject() )
1432  {
1433  d->mProj4 = getFullProjString( obj );
1434  }
1435 #else
1436  char *proj4src = nullptr;
1437  OSRExportToProj4( d->mCRS, &proj4src );
1438  d->mProj4 = proj4src;
1439  CPLFree( proj4src );
1440 #endif
1441  }
1442  // Stray spaces at the end?
1443  return d->mProj4.trimmed();
1444 }
1445 
1447 {
1448  return d->mIsGeographic;
1449 }
1450 
1452 {
1453  if ( !d->mIsValid )
1455 
1456  return d->mMapUnits;
1457 }
1458 
1460 {
1461  if ( !d->mIsValid )
1462  return QgsRectangle();
1463 
1464 #if PROJ_VERSION_MAJOR>=6
1465  PJ *obj = d->threadLocalProjObject();
1466  if ( !obj )
1467  return QgsRectangle();
1468 
1469  double westLon = 0;
1470  double southLat = 0;
1471  double eastLon = 0;
1472  double northLat = 0;
1473 
1474  if ( !proj_get_area_of_use( QgsProjContext::get(), obj,
1475  &westLon, &southLat, &eastLon, &northLat, nullptr ) )
1476  return QgsRectangle();
1477 
1478 
1479  // don't use the constructor which normalizes!
1480  QgsRectangle rect;
1481  rect.setXMinimum( westLon );
1482  rect.setYMinimum( southLat );
1483  rect.setXMaximum( eastLon );
1484  rect.setYMaximum( northLat );
1485  return rect;
1486 
1487 #else
1488  //check the db is available
1489  QString databaseFileName = QgsApplication::srsDatabaseFilePath();
1490 
1491  sqlite3_database_unique_ptr database;
1492  sqlite3_statement_unique_ptr statement;
1493 
1494  int result = openDatabase( databaseFileName, database );
1495  if ( result != SQLITE_OK )
1496  {
1497  return QgsRectangle();
1498  }
1499 
1500  QString sql = QStringLiteral( "select west_bound_lon, north_bound_lat, east_bound_lon, south_bound_lat from tbl_bounds "
1501  "where srid=%1" )
1502  .arg( d->mSRID );
1503  statement = database.prepare( sql, result );
1504 
1505  QgsRectangle rect;
1506  if ( result == SQLITE_OK )
1507  {
1508  if ( statement.step() == SQLITE_ROW )
1509  {
1510  double west = statement.columnAsDouble( 0 );
1511  double north = statement.columnAsDouble( 1 );
1512  double east = statement.columnAsDouble( 2 );
1513  double south = statement.columnAsDouble( 3 );
1514 
1515  rect.setXMinimum( west );
1516  rect.setYMinimum( south );
1517  rect.setXMaximum( east );
1518  rect.setYMaximum( north );
1519  }
1520  }
1521  return rect;
1522 #endif
1523 }
1524 
1526 {
1527  if ( !d->mIsValid )
1528  return;
1529 
1530  if ( d->mSrsId >= USER_CRS_START_ID )
1531  {
1532  // user CRS, so update to new definition
1533  createFromSrsId( d->mSrsId );
1534  }
1535  else
1536  {
1537  // nothing to do -- only user CRS definitions can be changed
1538  }
1539 }
1540 
1541 void QgsCoordinateReferenceSystem::setProjString( const QString &proj4String )
1542 {
1543  d.detach();
1544  d->mProj4 = proj4String;
1545  d->mWktPreferred.clear();
1546 
1547  QgsLocaleNumC l;
1548  QString trimmed = proj4String.trimmed();
1549 
1550 #if PROJ_VERSION_MAJOR>=6
1551  trimmed += QLatin1String( " +type=crs" );
1553 
1554  {
1555  d->setPj( QgsProjUtils::proj_pj_unique_ptr( proj_create( ctx, trimmed.toLatin1().constData() ) ) );
1556  }
1557 
1558  if ( !d->hasPj() )
1559  {
1560 #ifdef QGISDEBUG
1561  const int errNo = proj_context_errno( ctx );
1562  QgsDebugMsg( QStringLiteral( "proj string rejected: %1" ).arg( proj_errno_string( errNo ) ) );
1563 #endif
1564  d->mIsValid = false;
1565  }
1566  else
1567  {
1568  d->mEllipsoidAcronym.clear();
1569  d->mIsValid = true;
1570  }
1571 #else
1572  OSRDestroySpatialReference( d->mCRS );
1573  d->mCRS = OSRNewSpatialReference( nullptr );
1574  d->mIsValid = OSRImportFromProj4( d->mCRS, trimmed.toLatin1().constData() ) == OGRERR_NONE;
1575 
1576  // OSRImportFromProj4() may accept strings that are not valid proj.4 strings,
1577  // e.g if they lack a +ellps parameter, it will automatically add +ellps=WGS84, but as
1578  // we use the original mProj4 with QgsCoordinateTransform, it will fail to initialize
1579  // so better detect it now.
1580  projCtx pContext = pj_ctx_alloc();
1581  projPJ proj = pj_init_plus_ctx( pContext, proj4String.trimmed().toLatin1().constData() );
1582  if ( !proj )
1583  {
1584  QgsDebugMsgLevel( QStringLiteral( "proj.4 string rejected by pj_init_plus_ctx()" ), 4 );
1585  d->mIsValid = false;
1586  }
1587  else
1588  {
1589  pj_free( proj );
1590  }
1591  pj_ctx_free( pContext );
1592 #endif
1593 
1594  setMapUnits();
1595 }
1596 
1597 bool QgsCoordinateReferenceSystem::setWktString( const QString &wkt, bool allowProjFallback )
1598 {
1599  bool res = false;
1600  d->mIsValid = false;
1601  d->mWktPreferred.clear();
1602 
1603 #if PROJ_VERSION_MAJOR>=6
1604  // TODO - remove allowProjFallback when we require proj 6+ to build
1605  ( void )allowProjFallback;
1606 
1607  PROJ_STRING_LIST warnings = nullptr;
1608  PROJ_STRING_LIST grammerErrors = nullptr;
1609  {
1610  d->setPj( QgsProjUtils::proj_pj_unique_ptr( proj_create_from_wkt( QgsProjContext::get(), wkt.toLatin1().constData(), nullptr, &warnings, &grammerErrors ) ) );
1611  }
1612 
1613  res = d->hasPj();
1614  if ( !res )
1615  {
1616  QgsDebugMsg( QStringLiteral( "\n---------------------------------------------------------------" ) );
1617  QgsDebugMsg( QStringLiteral( "This CRS could *** NOT *** be set from the supplied Wkt " ) );
1618  QgsDebugMsg( "INPUT: " + wkt );
1619  for ( auto iter = warnings; iter && *iter; ++iter )
1620  QgsDebugMsg( *iter );
1621  for ( auto iter = grammerErrors; iter && *iter; ++iter )
1622  QgsDebugMsg( *iter );
1623  QgsDebugMsg( QStringLiteral( "---------------------------------------------------------------\n" ) );
1624  }
1625  proj_string_list_destroy( warnings );
1626  proj_string_list_destroy( grammerErrors );
1627 #else
1628  QByteArray ba = wkt.toLatin1();
1629  const char *pWkt = ba.data();
1630 
1631  OGRErr myInputResult = OSRImportFromWkt( d->mCRS, const_cast< char ** >( & pWkt ) );
1632  res = myInputResult == OGRERR_NONE;
1633  if ( !res )
1634  {
1635  QgsDebugMsg( QStringLiteral( "\n---------------------------------------------------------------" ) );
1636  QgsDebugMsg( QStringLiteral( "This CRS could *** NOT *** be set from the supplied Wkt " ) );
1637  QgsDebugMsg( "INPUT: " + wkt );
1638  QgsDebugMsg( QStringLiteral( "UNUSED WKT: %1" ).arg( pWkt ) );
1639  QgsDebugMsg( QStringLiteral( "---------------------------------------------------------------\n" ) );
1640  }
1641  else
1642  {
1643  d->mIsValid = true;
1644  }
1645 #endif
1646 
1647  QgsReadWriteLocker locker( *sProj4CacheLock(), QgsReadWriteLocker::Unlocked );
1648  if ( !res )
1649  {
1650  locker.changeMode( QgsReadWriteLocker::Write );
1651  if ( !sDisableWktCache )
1652  sWktCache()->insert( wkt, *this );
1653  return d->mIsValid;
1654  }
1655 
1656 #if PROJ_VERSION_MAJOR>=6
1657  if ( d->hasPj() )
1658  {
1659  // try 1 - maybe we can directly grab the auth name and code from the crs already?
1660  QString authName( proj_get_id_auth_name( d->threadLocalProjObject(), 0 ) );
1661  QString authCode( proj_get_id_code( d->threadLocalProjObject(), 0 ) );
1662 
1663  if ( authName.isEmpty() || authCode.isEmpty() )
1664  {
1665  // try 2, use proj's identify method and see if there's a nice candidate we can use
1666  QgsProjUtils::identifyCrs( d->threadLocalProjObject(), authName, authCode );
1667  }
1668 
1669  if ( !authName.isEmpty() && !authCode.isEmpty() )
1670  {
1671  if ( loadFromAuthCode( authName, authCode ) )
1672  {
1673  locker.changeMode( QgsReadWriteLocker::Write );
1674  if ( !sDisableWktCache )
1675  sWktCache()->insert( wkt, *this );
1676  return d->mIsValid;
1677  }
1678  }
1679  else
1680  {
1681  // Still a valid CRS, just not a known one
1682  d->mIsValid = true;
1683  d->mDescription = QString( proj_get_name( d->threadLocalProjObject() ) );
1684  }
1685  setMapUnits();
1686  }
1687 #else
1688  if ( OSRAutoIdentifyEPSG( d->mCRS ) == OGRERR_NONE )
1689  {
1690  QString authid = QStringLiteral( "%1:%2" )
1691  .arg( OSRGetAuthorityName( d->mCRS, nullptr ),
1692  OSRGetAuthorityCode( d->mCRS, nullptr ) );
1693  bool result = createFromOgcWmsCrs( authid );
1694  locker.changeMode( QgsReadWriteLocker::Write );
1695  if ( !sDisableWktCache )
1696  sWktCache()->insert( wkt, *this );
1697  return result;
1698  }
1699 #endif
1700 
1701  // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
1702  // WARNING - wkt to proj conversion is lossy, hence we DON'T DO THIS on proj 6
1703  // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
1704 
1705 #if PROJ_VERSION_MAJOR<6
1706  if ( allowProjFallback )
1707  {
1708  d->mIsValid = false;
1709  // create the proj4 structs needed for transforming
1710  char *proj4src = nullptr;
1711  OSRExportToProj4( d->mCRS, &proj4src );
1712 
1713  //now that we have the proj4string, delegate to createFromProj so
1714  // that we can try to fill in the remaining class members...
1715  //create from Proj will set the isValidFlag
1716  if ( !createFromProj( proj4src ) )
1717  {
1718  CPLFree( proj4src );
1719 
1720 #if GDAL_VERSION_NUM < GDAL_COMPUTE_VERSION(2,5,0)
1721  // try fixed up version
1722  OSRFixup( d->mCRS );
1723 #endif
1724 
1725  OSRExportToProj4( d->mCRS, &proj4src );
1726 
1727  createFromProj( proj4src );
1728  }
1729  CPLFree( proj4src );
1730  }
1731  else if ( d->mIsValid )
1732  {
1733  setMapUnits();
1734  }
1735 #endif
1736  return d->mIsValid;
1737 }
1738 
1739 void QgsCoordinateReferenceSystem::setMapUnits()
1740 {
1741  if ( !d->mIsValid )
1742  {
1743  d->mMapUnits = QgsUnitTypes::DistanceUnknownUnit;
1744  return;
1745  }
1746 
1747 #if PROJ_VERSION_MAJOR<6
1748 #if GDAL_VERSION_NUM < GDAL_COMPUTE_VERSION(2,5,0)
1749  // Of interest to us is that this call adds in a unit parameter if
1750  // one doesn't already exist.
1751  OSRFixup( d->mCRS );
1752 #endif
1753 #endif
1754 
1755 #if PROJ_VERSION_MAJOR>=6
1756  if ( !d->hasPj() )
1757  {
1758  d->mMapUnits = QgsUnitTypes::DistanceUnknownUnit;
1759  return;
1760  }
1761 
1762  PJ_CONTEXT *context = QgsProjContext::get();
1763  QgsProjUtils::proj_pj_unique_ptr crs( QgsProjUtils::crsToSingleCrs( d->threadLocalProjObject() ) );
1764  QgsProjUtils::proj_pj_unique_ptr coordinateSystem( proj_crs_get_coordinate_system( context, crs.get() ) );
1765  if ( !coordinateSystem )
1766  {
1767  d->mMapUnits = QgsUnitTypes::DistanceUnknownUnit;
1768  return;
1769  }
1770 
1771  const int axisCount = proj_cs_get_axis_count( context, coordinateSystem.get() );
1772  if ( axisCount > 0 )
1773  {
1774  const char *outUnitName = nullptr;
1775  // Read only first axis
1776  proj_cs_get_axis_info( context, coordinateSystem.get(), 0,
1777  nullptr,
1778  nullptr,
1779  nullptr,
1780  nullptr,
1781  &outUnitName,
1782  nullptr,
1783  nullptr );
1784 
1785  const QString unitName( outUnitName );
1786 
1787  // proj unit names are freeform -- they differ from authority to authority :(
1788  // see https://lists.osgeo.org/pipermail/proj/2019-April/008444.html
1789  if ( unitName.compare( QLatin1String( "degree" ), Qt::CaseInsensitive ) == 0 ||
1790  unitName.compare( QLatin1String( "degree minute second" ), Qt::CaseInsensitive ) == 0 ||
1791  unitName.compare( QLatin1String( "degree minute second hemisphere" ), Qt::CaseInsensitive ) == 0 ||
1792  unitName.compare( QLatin1String( "degree minute" ), Qt::CaseInsensitive ) == 0 ||
1793  unitName.compare( QLatin1String( "degree hemisphere" ), Qt::CaseInsensitive ) == 0 ||
1794  unitName.compare( QLatin1String( "degree minute hemisphere" ), Qt::CaseInsensitive ) == 0 ||
1795  unitName.compare( QLatin1String( "hemisphere degree" ), Qt::CaseInsensitive ) == 0 ||
1796  unitName.compare( QLatin1String( "hemisphere degree minute" ), Qt::CaseInsensitive ) == 0 ||
1797  unitName.compare( QLatin1String( "hemisphere degree minute second" ), Qt::CaseInsensitive ) == 0 ||
1798  unitName.compare( QLatin1String( "degree (supplier to define representation)" ), Qt::CaseInsensitive ) == 0 )
1799  d->mMapUnits = QgsUnitTypes::DistanceDegrees;
1800  else if ( unitName.compare( QLatin1String( "metre" ), Qt::CaseInsensitive ) == 0
1801  || unitName.compare( QLatin1String( "m" ), Qt::CaseInsensitive ) == 0
1802  || unitName.compare( QLatin1String( "meter" ), Qt::CaseInsensitive ) == 0 )
1803  d->mMapUnits = QgsUnitTypes::DistanceMeters;
1804  // we don't differentiate between these, suck it imperial users!
1805  else if ( unitName.compare( QLatin1String( "US survey foot" ), Qt::CaseInsensitive ) == 0 ||
1806  unitName.compare( QLatin1String( "foot" ), Qt::CaseInsensitive ) == 0 )
1807  d->mMapUnits = QgsUnitTypes::DistanceFeet;
1808  else if ( unitName.compare( QLatin1String( "kilometre" ), Qt::CaseInsensitive ) == 0 ) //#spellok
1809  d->mMapUnits = QgsUnitTypes::DistanceKilometers;
1810  else if ( unitName.compare( QLatin1String( "centimetre" ), Qt::CaseInsensitive ) == 0 ) //#spellok
1811  d->mMapUnits = QgsUnitTypes::DistanceCentimeters;
1812  else if ( unitName.compare( QLatin1String( "millimetre" ), Qt::CaseInsensitive ) == 0 ) //#spellok
1813  d->mMapUnits = QgsUnitTypes::DistanceMillimeters;
1814  else if ( unitName.compare( QLatin1String( "Statute mile" ), Qt::CaseInsensitive ) == 0 )
1815  d->mMapUnits = QgsUnitTypes::DistanceMiles;
1816  else if ( unitName.compare( QLatin1String( "nautical mile" ), Qt::CaseInsensitive ) == 0 )
1817  d->mMapUnits = QgsUnitTypes::DistanceNauticalMiles;
1818  else if ( unitName.compare( QLatin1String( "yard" ), Qt::CaseInsensitive ) == 0 )
1819  d->mMapUnits = QgsUnitTypes::DistanceYards;
1820  // TODO - maybe more values to handle here?
1821  else
1822  d->mMapUnits = QgsUnitTypes::DistanceUnknownUnit;
1823  return;
1824  }
1825  else
1826  {
1827  d->mMapUnits = QgsUnitTypes::DistanceUnknownUnit;
1828  return;
1829  }
1830 
1831 #else
1832  char *unitName = nullptr;
1833 
1834  if ( OSRIsProjected( d->mCRS ) )
1835  {
1836  double toMeter = OSRGetLinearUnits( d->mCRS, &unitName );
1837  QString unit( unitName );
1838 
1839  // If the units parameter was created during the Fixup() call
1840  // above, the name of the units is likely to be 'unknown'. Try to
1841  // do better than that ... (but perhaps ogr should be enhanced to
1842  // do this instead?).
1843 
1844  static const double FEET_TO_METER = 0.3048;
1845  static const double SMALL_NUM = 1e-3;
1846 
1847  if ( std::fabs( toMeter - FEET_TO_METER ) < SMALL_NUM )
1848  unit = QStringLiteral( "Foot" );
1849 
1850  if ( qgsDoubleNear( toMeter, 1.0 ) ) //Unit name for meters would be "metre"
1851  d->mMapUnits = QgsUnitTypes::DistanceMeters;
1852  else if ( unit == QLatin1String( "Foot" ) )
1853  d->mMapUnits = QgsUnitTypes::DistanceFeet;
1854  else
1855  {
1856  d->mMapUnits = QgsUnitTypes::DistanceUnknownUnit;
1857  }
1858  }
1859  else
1860  {
1861  OSRGetAngularUnits( d->mCRS, &unitName );
1862  QString unit( unitName );
1863  if ( unit == QLatin1String( "degree" ) )
1864  d->mMapUnits = QgsUnitTypes::DistanceDegrees;
1865  else
1866  {
1867  d->mMapUnits = QgsUnitTypes::DistanceUnknownUnit;
1868  }
1869  }
1870 #endif
1871 }
1872 
1873 
1875 {
1876  if ( d->mEllipsoidAcronym.isNull() || d->mProjectionAcronym.isNull()
1877  || !d->mIsValid )
1878  {
1879  QgsDebugMsgLevel( "QgsCoordinateReferenceSystem::findMatchingProj will only "
1880  "work if prj acr ellipsoid acr and proj4string are set"
1881  " and the current projection is valid!", 4 );
1882  return 0;
1883  }
1884 
1885  sqlite3_database_unique_ptr database;
1886  sqlite3_statement_unique_ptr statement;
1887  int myResult;
1888 
1889  // Set up the query to retrieve the projection information
1890  // needed to populate the list
1891  QString mySql = QString( "select srs_id,parameters from tbl_srs where "
1892  "projection_acronym=%1 and ellipsoid_acronym=%2 order by deprecated" )
1893  .arg( QgsSqliteUtils::quotedString( d->mProjectionAcronym ),
1894  QgsSqliteUtils::quotedString( d->mEllipsoidAcronym ) );
1895  // Get the full path name to the sqlite3 spatial reference database.
1896  QString myDatabaseFileName = QgsApplication::srsDatabaseFilePath();
1897 
1898  //check the db is available
1899  myResult = openDatabase( myDatabaseFileName, database );
1900  if ( myResult != SQLITE_OK )
1901  {
1902  return 0;
1903  }
1904 
1905  statement = database.prepare( mySql, myResult );
1906  if ( myResult == SQLITE_OK )
1907  {
1908 
1909  while ( statement.step() == SQLITE_ROW )
1910  {
1911  QString mySrsId = statement.columnAsText( 0 );
1912  QString myProj4String = statement.columnAsText( 1 );
1913  if ( toProj() == myProj4String.trimmed() )
1914  {
1915  return mySrsId.toLong();
1916  }
1917  }
1918  }
1919 
1920  //
1921  // Try the users db now
1922  //
1923 
1924  myDatabaseFileName = QgsApplication::qgisUserDatabaseFilePath();
1925  //check the db is available
1926  myResult = openDatabase( myDatabaseFileName, database );
1927  if ( myResult != SQLITE_OK )
1928  {
1929  return 0;
1930  }
1931 
1932  statement = database.prepare( mySql, myResult );
1933 
1934  if ( myResult == SQLITE_OK )
1935  {
1936  while ( statement.step() == SQLITE_ROW )
1937  {
1938  QString mySrsId = statement.columnAsText( 0 );
1939  QString myProj4String = statement.columnAsText( 1 );
1940  if ( toProj() == myProj4String.trimmed() )
1941  {
1942  return mySrsId.toLong();
1943  }
1944  }
1945  }
1946 
1947  return 0;
1948 }
1949 
1951 {
1952  // shortcut
1953  if ( d == srs.d )
1954  return true;
1955 
1956  if ( !d->mIsValid && !srs.d->mIsValid )
1957  return true;
1958 
1959  if ( !d->mIsValid || !srs.d->mIsValid )
1960  return false;
1961 
1962  const bool isUser = d->mSrsId >= USER_CRS_START_ID;
1963  const bool otherIsUser = srs.d->mSrsId >= USER_CRS_START_ID;
1964  if ( isUser != otherIsUser )
1965  return false;
1966 
1967  // we can't directly compare authid for user crses -- the actual definition of these may have changed
1968  if ( !isUser && ( !d->mAuthId.isEmpty() || !srs.d->mAuthId.isEmpty() ) )
1969  return d->mAuthId == srs.d->mAuthId;
1970 
1971  return toWkt( WKT_PREFERRED ) == srs.toWkt( WKT_PREFERRED );
1972 }
1973 
1975 {
1976  return !( *this == srs );
1977 }
1978 
1979 QString QgsCoordinateReferenceSystem::toWkt( WktVariant variant, bool multiline, int indentationWidth ) const
1980 {
1981 #if PROJ_VERSION_MAJOR>=6
1982  if ( PJ *obj = d->threadLocalProjObject() )
1983  {
1984  const bool isDefaultPreferredFormat = variant == WKT_PREFERRED && !multiline;
1985  if ( isDefaultPreferredFormat && !d->mWktPreferred.isEmpty() )
1986  {
1987  // can use cached value
1988  return d->mWktPreferred;
1989  }
1990 
1991  PJ_WKT_TYPE type = PJ_WKT1_GDAL;
1992  switch ( variant )
1993  {
1994  case WKT1_GDAL:
1995  type = PJ_WKT1_GDAL;
1996  break;
1997  case WKT1_ESRI:
1998  type = PJ_WKT1_ESRI;
1999  break;
2000  case WKT2_2015:
2001  type = PJ_WKT2_2015;
2002  break;
2003  case WKT2_2015_SIMPLIFIED:
2004  type = PJ_WKT2_2015_SIMPLIFIED;
2005  break;
2006  case WKT2_2019:
2007  type = PJ_WKT2_2019;
2008  break;
2009  case WKT2_2019_SIMPLIFIED:
2010  type = PJ_WKT2_2019_SIMPLIFIED;
2011  break;
2012  }
2013 
2014  const QByteArray multiLineOption = QStringLiteral( "MULTILINE=%1" ).arg( multiline ? QStringLiteral( "YES" ) : QStringLiteral( "NO" ) ).toLocal8Bit();
2015  const QByteArray indentatationWidthOption = QStringLiteral( "INDENTATION_WIDTH=%1" ).arg( multiline ? QString::number( indentationWidth ) : QStringLiteral( "0" ) ).toLocal8Bit();
2016  const char *const options[] = {multiLineOption.constData(), indentatationWidthOption.constData(), nullptr};
2017  QString res = proj_as_wkt( QgsProjContext::get(), obj, type, options );
2018 
2019  if ( isDefaultPreferredFormat )
2020  {
2021  // cache result for later use
2022  d->mWktPreferred = res;
2023  }
2024 
2025  return res;
2026  }
2027  return QString();
2028 #else
2029  Q_UNUSED( variant )
2030  Q_UNUSED( multiline )
2031  Q_UNUSED( indentationWidth )
2032  char *wkt = nullptr;
2033  QString stringWkt;
2034  if ( OSRExportToWkt( d->mCRS, &wkt ) == OGRERR_NONE )
2035  {
2036  stringWkt = wkt;
2037  CPLFree( wkt );
2038  }
2039  return stringWkt;
2040 #endif
2041 }
2042 
2043 bool QgsCoordinateReferenceSystem::readXml( const QDomNode &node )
2044 {
2045  d.detach();
2046  bool result = true;
2047  QDomNode srsNode = node.namedItem( QStringLiteral( "spatialrefsys" ) );
2048 
2049  if ( ! srsNode.isNull() )
2050  {
2051  bool initialized = false;
2052 
2053  bool ok = false;
2054  long srsid = srsNode.namedItem( QStringLiteral( "srsid" ) ).toElement().text().toLong( &ok );
2055 
2056  QDomNode node;
2057 
2058  if ( ok && srsid > 0 && srsid < USER_CRS_START_ID )
2059  {
2060  node = srsNode.namedItem( QStringLiteral( "authid" ) );
2061  if ( !node.isNull() )
2062  {
2063  createFromOgcWmsCrs( node.toElement().text() );
2064  if ( isValid() )
2065  {
2066  initialized = true;
2067  }
2068  }
2069 
2070  if ( !initialized )
2071  {
2072  node = srsNode.namedItem( QStringLiteral( "epsg" ) );
2073  if ( !node.isNull() )
2074  {
2075  operator=( QgsCoordinateReferenceSystem::fromEpsgId( node.toElement().text().toLong() ) );
2076  if ( isValid() )
2077  {
2078  initialized = true;
2079  }
2080  }
2081  }
2082  }
2083 
2084  // if wkt is present, prefer that since it's lossless (unlike proj4 strings)
2085  if ( !initialized )
2086  {
2087  // before doing anything, we grab and set the stored CRS name (description).
2088  // this way if the stored CRS doesn't match anything available locally (i.e. from Proj's db
2089  // or the user's custom CRS list), then we will correctly show the CRS with its original
2090  // name (instead of just "custom crs")
2091  const QString description = srsNode.namedItem( QStringLiteral( "description" ) ).toElement().text();
2092 
2093  const QString wkt = srsNode.namedItem( QStringLiteral( "wkt" ) ).toElement().text();
2094  initialized = createFromWktInternal( wkt, description );
2095  }
2096 
2097  if ( !initialized )
2098  {
2099  node = srsNode.namedItem( QStringLiteral( "proj4" ) );
2100  const QString proj4 = node.toElement().text();
2101  initialized = createFromProj( proj4 );
2102  }
2103 
2104  if ( !initialized )
2105  {
2106  // Setting from elements one by one
2107  node = srsNode.namedItem( QStringLiteral( "proj4" ) );
2108  const QString proj4 = node.toElement().text();
2109  if ( !proj4.trimmed().isEmpty() )
2110  setProjString( node.toElement().text() );
2111 
2112  node = srsNode.namedItem( QStringLiteral( "srsid" ) );
2113  d->mSrsId = node.toElement().text().toLong();
2114 
2115  node = srsNode.namedItem( QStringLiteral( "srid" ) );
2116  d->mSRID = node.toElement().text().toLong();
2117 
2118  node = srsNode.namedItem( QStringLiteral( "authid" ) );
2119  d->mAuthId = node.toElement().text();
2120 
2121  node = srsNode.namedItem( QStringLiteral( "description" ) );
2122  d->mDescription = node.toElement().text();
2123 
2124  node = srsNode.namedItem( QStringLiteral( "projectionacronym" ) );
2125  d->mProjectionAcronym = node.toElement().text();
2126 
2127  node = srsNode.namedItem( QStringLiteral( "ellipsoidacronym" ) );
2128  d->mEllipsoidAcronym = node.toElement().text();
2129 
2130  node = srsNode.namedItem( QStringLiteral( "geographicflag" ) );
2131  d->mIsGeographic = node.toElement().text().compare( QLatin1String( "true" ) );
2132 
2133  d->mWktPreferred.clear();
2134 
2135  //make sure the map units have been set
2136  setMapUnits();
2137  }
2138  }
2139  else
2140  {
2141  // Return empty CRS if none was found in the XML.
2142  d = new QgsCoordinateReferenceSystemPrivate();
2143  result = false;
2144  }
2145  return result;
2146 }
2147 
2148 bool QgsCoordinateReferenceSystem::writeXml( QDomNode &node, QDomDocument &doc ) const
2149 {
2150  QDomElement layerNode = node.toElement();
2151  QDomElement srsElement = doc.createElement( QStringLiteral( "spatialrefsys" ) );
2152 
2153  QDomElement wktElement = doc.createElement( QStringLiteral( "wkt" ) );
2154  wktElement.appendChild( doc.createTextNode( toWkt( WKT_PREFERRED ) ) );
2155  srsElement.appendChild( wktElement );
2156 
2157  QDomElement proj4Element = doc.createElement( QStringLiteral( "proj4" ) );
2158  proj4Element.appendChild( doc.createTextNode( toProj() ) );
2159  srsElement.appendChild( proj4Element );
2160 
2161  QDomElement srsIdElement = doc.createElement( QStringLiteral( "srsid" ) );
2162  srsIdElement.appendChild( doc.createTextNode( QString::number( srsid() ) ) );
2163  srsElement.appendChild( srsIdElement );
2164 
2165  QDomElement sridElement = doc.createElement( QStringLiteral( "srid" ) );
2166  sridElement.appendChild( doc.createTextNode( QString::number( postgisSrid() ) ) );
2167  srsElement.appendChild( sridElement );
2168 
2169  QDomElement authidElement = doc.createElement( QStringLiteral( "authid" ) );
2170  authidElement.appendChild( doc.createTextNode( authid() ) );
2171  srsElement.appendChild( authidElement );
2172 
2173  QDomElement descriptionElement = doc.createElement( QStringLiteral( "description" ) );
2174  descriptionElement.appendChild( doc.createTextNode( description() ) );
2175  srsElement.appendChild( descriptionElement );
2176 
2177  QDomElement projectionAcronymElement = doc.createElement( QStringLiteral( "projectionacronym" ) );
2178  projectionAcronymElement.appendChild( doc.createTextNode( projectionAcronym() ) );
2179  srsElement.appendChild( projectionAcronymElement );
2180 
2181  QDomElement ellipsoidAcronymElement = doc.createElement( QStringLiteral( "ellipsoidacronym" ) );
2182  ellipsoidAcronymElement.appendChild( doc.createTextNode( ellipsoidAcronym() ) );
2183  srsElement.appendChild( ellipsoidAcronymElement );
2184 
2185  QDomElement geographicFlagElement = doc.createElement( QStringLiteral( "geographicflag" ) );
2186  QString geoFlagText = QStringLiteral( "false" );
2187  if ( isGeographic() )
2188  {
2189  geoFlagText = QStringLiteral( "true" );
2190  }
2191 
2192  geographicFlagElement.appendChild( doc.createTextNode( geoFlagText ) );
2193  srsElement.appendChild( geographicFlagElement );
2194 
2195  layerNode.appendChild( srsElement );
2196 
2197  return true;
2198 }
2199 
2200 //
2201 // Static helper methods below this point only please!
2202 //
2203 
2204 
2205 // Returns the whole proj4 string for the selected srsid
2206 //this is a static method! NOTE I've made it private for now to reduce API clutter TS
2207 QString QgsCoordinateReferenceSystem::projFromSrsId( const int srsId )
2208 {
2209  QString myDatabaseFileName;
2210  QString myProjString;
2211  QString mySql = QStringLiteral( "select parameters from tbl_srs where srs_id = %1 order by deprecated" ).arg( srsId );
2212 
2213  //
2214  // Determine if this is a user projection or a system on
2215  // user projection defs all have srs_id >= 100000
2216  //
2217  if ( srsId >= USER_CRS_START_ID )
2218  {
2219  myDatabaseFileName = QgsApplication::qgisUserDatabaseFilePath();
2220  QFileInfo myFileInfo;
2221  myFileInfo.setFile( myDatabaseFileName );
2222  if ( !myFileInfo.exists() ) //its unlikely that this condition will ever be reached
2223  {
2224  QgsDebugMsg( QStringLiteral( "users qgis.db not found" ) );
2225  return QString();
2226  }
2227  }
2228  else //must be a system projection then
2229  {
2230  myDatabaseFileName = QgsApplication::srsDatabaseFilePath();
2231  }
2232 
2233  sqlite3_database_unique_ptr database;
2234  sqlite3_statement_unique_ptr statement;
2235 
2236  int rc;
2237  rc = openDatabase( myDatabaseFileName, database );
2238  if ( rc )
2239  {
2240  return QString();
2241  }
2242 
2243  statement = database.prepare( mySql, rc );
2244 
2245  if ( rc == SQLITE_OK )
2246  {
2247  if ( statement.step() == SQLITE_ROW )
2248  {
2249  myProjString = statement.columnAsText( 0 );
2250  }
2251  }
2252 
2253  return myProjString;
2254 }
2255 
2256 int QgsCoordinateReferenceSystem::openDatabase( const QString &path, sqlite3_database_unique_ptr &database, bool readonly )
2257 {
2258  int myResult;
2259  if ( readonly )
2260  myResult = database.open_v2( path, SQLITE_OPEN_READONLY, nullptr );
2261  else
2262  myResult = database.open( path );
2263 
2264  if ( myResult != SQLITE_OK )
2265  {
2266  QgsDebugMsg( "Can't open database: " + database.errorMessage() );
2267  // XXX This will likely never happen since on open, sqlite creates the
2268  // database if it does not exist.
2269  // ... unfortunately it happens on Windows
2270  QgsMessageLog::logMessage( QObject::tr( "Could not open CRS database %1\nError(%2): %3" )
2271  .arg( path )
2272  .arg( myResult )
2273  .arg( database.errorMessage() ), QObject::tr( "CRS" ) );
2274  }
2275  return myResult;
2276 }
2277 
2279 {
2280  sCustomSrsValidation = f;
2281 }
2282 
2284 {
2285  return sCustomSrsValidation;
2286 }
2287 
2288 void QgsCoordinateReferenceSystem::debugPrint()
2289 {
2290  QgsDebugMsg( QStringLiteral( "***SpatialRefSystem***" ) );
2291  QgsDebugMsg( "* Valid : " + ( d->mIsValid ? QString( "true" ) : QString( "false" ) ) );
2292  QgsDebugMsg( "* SrsId : " + QString::number( d->mSrsId ) );
2293  QgsDebugMsg( "* Proj4 : " + toProj() );
2294  QgsDebugMsg( "* WKT : " + toWkt( WKT_PREFERRED ) );
2295  QgsDebugMsg( "* Desc. : " + d->mDescription );
2297  {
2298  QgsDebugMsg( QStringLiteral( "* Units : meters" ) );
2299  }
2300  else if ( mapUnits() == QgsUnitTypes::DistanceFeet )
2301  {
2302  QgsDebugMsg( QStringLiteral( "* Units : feet" ) );
2303  }
2304  else if ( mapUnits() == QgsUnitTypes::DistanceDegrees )
2305  {
2306  QgsDebugMsg( QStringLiteral( "* Units : degrees" ) );
2307  }
2308 }
2309 
2311 {
2312  mValidationHint = html;
2313 }
2314 
2316 {
2317  return mValidationHint;
2318 }
2319 
2320 long QgsCoordinateReferenceSystem::saveAsUserCrs( const QString &name, Format nativeFormat )
2321 {
2322  return QgsApplication::coordinateReferenceSystemRegistry()->addUserCrs( *this, name, nativeFormat );
2323 }
2324 
2325 long QgsCoordinateReferenceSystem::getRecordCount()
2326 {
2327  sqlite3_database_unique_ptr database;
2328  sqlite3_statement_unique_ptr statement;
2329  int myResult;
2330  long myRecordCount = 0;
2331  //check the db is available
2332  myResult = database.open_v2( QgsApplication::qgisUserDatabaseFilePath(), SQLITE_OPEN_READONLY, nullptr );
2333  if ( myResult != SQLITE_OK )
2334  {
2335  QgsDebugMsg( QStringLiteral( "Can't open database: %1" ).arg( database.errorMessage() ) );
2336  return 0;
2337  }
2338  // Set up the query to retrieve the projection information needed to populate the ELLIPSOID list
2339  QString mySql = QStringLiteral( "select count(*) from tbl_srs" );
2340  statement = database.prepare( mySql, myResult );
2341  if ( myResult == SQLITE_OK )
2342  {
2343  if ( statement.step() == SQLITE_ROW )
2344  {
2345  QString myRecordCountString = statement.columnAsText( 0 );
2346  myRecordCount = myRecordCountString.toLong();
2347  }
2348  }
2349  return myRecordCount;
2350 }
2351 
2352 #if PROJ_VERSION_MAJOR>=6
2353 bool testIsGeographic( PJ *crs )
2354 {
2355  PJ_CONTEXT *pjContext = QgsProjContext::get();
2356  bool isGeographic = false;
2357  QgsProjUtils::proj_pj_unique_ptr coordinateSystem( proj_crs_get_coordinate_system( pjContext, crs ) );
2358  if ( coordinateSystem )
2359  {
2360  const int axisCount = proj_cs_get_axis_count( pjContext, coordinateSystem.get() );
2361  if ( axisCount > 0 )
2362  {
2363  const char *outUnitAuthName = nullptr;
2364  const char *outUnitAuthCode = nullptr;
2365  // Read only first axis
2366  proj_cs_get_axis_info( pjContext, coordinateSystem.get(), 0,
2367  nullptr,
2368  nullptr,
2369  nullptr,
2370  nullptr,
2371  nullptr,
2372  &outUnitAuthName,
2373  &outUnitAuthCode );
2374 
2375  if ( outUnitAuthName && outUnitAuthCode )
2376  {
2377  const char *unitCategory = nullptr;
2378  if ( proj_uom_get_info_from_database( pjContext, outUnitAuthName, outUnitAuthCode, nullptr, nullptr, &unitCategory ) )
2379  {
2380  isGeographic = QString( unitCategory ).compare( QLatin1String( "angular" ), Qt::CaseInsensitive ) == 0;
2381  }
2382  }
2383  }
2384  }
2385  return isGeographic;
2386 }
2387 
2388 void getOperationAndEllipsoidFromProjString( const QString &proj, QString &operation, QString &ellipsoid )
2389 {
2390  thread_local const QRegularExpression projRegExp( QStringLiteral( "\\+proj=(\\S+)" ) );
2391  const QRegularExpressionMatch projMatch = projRegExp.match( proj );
2392  if ( !projMatch.hasMatch() )
2393  {
2394  QgsDebugMsgLevel( QStringLiteral( "no +proj argument found [%2]" ).arg( proj ), 2 );
2395  return;
2396  }
2397  operation = projMatch.captured( 1 );
2398 
2399  thread_local const QRegularExpression ellipseRegExp( QStringLiteral( "\\+(?:ellps|datum)=(\\S+)" ) );
2400  const QRegularExpressionMatch ellipseMatch = projRegExp.match( proj );
2401  QString ellps;
2402  if ( !ellipseMatch.hasMatch() )
2403  {
2404  ellipsoid = ellipseMatch.captured( 1 );
2405  }
2406  else
2407  {
2408  // satisfy not null constraint on ellipsoid_acronym field
2409  // possibly we should drop the constraint, yet the crses with missing ellipsoid_acronym are malformed
2410  // and will result in oddities within other areas of QGIS (e.g. project ellipsoid won't be correctly
2411  // set for these CRSes). Better just hack around and make the constraint happy for now,
2412  // and hope that the definitions get corrected in future.
2413  ellipsoid.clear();
2414  }
2415 }
2416 
2417 
2418 bool QgsCoordinateReferenceSystem::loadFromAuthCode( const QString &auth, const QString &code )
2419 {
2420  d.detach();
2421  d->mIsValid = false;
2422  d->mWktPreferred.clear();
2423 
2424  PJ_CONTEXT *pjContext = QgsProjContext::get();
2425  QgsProjUtils::proj_pj_unique_ptr crs( proj_create_from_database( pjContext, auth.toUtf8().constData(), code.toUtf8().constData(), PJ_CATEGORY_CRS, false, nullptr ) );
2426  if ( !crs )
2427  {
2428  return false;
2429  }
2430 
2431  switch ( proj_get_type( crs.get() ) )
2432  {
2433  case PJ_TYPE_VERTICAL_CRS:
2434  return false;
2435 
2436  default:
2437  break;
2438  }
2439 
2440  crs = QgsProjUtils::crsToSingleCrs( crs.get() );
2441 
2442  QString proj4 = getFullProjString( crs.get() );
2443  proj4.replace( QLatin1String( "+type=crs" ), QString() );
2444  proj4 = proj4.trimmed();
2445 
2446  d->mIsValid = true;
2447  d->mProj4 = proj4;
2448  d->mWktPreferred.clear();
2449  d->mDescription = QString( proj_get_name( crs.get() ) );
2450  d->mAuthId = QStringLiteral( "%1:%2" ).arg( auth, code );
2451  d->mIsGeographic = testIsGeographic( crs.get() );
2452  d->mAxisInvertedDirty = true;
2453  QString operation;
2454  QString ellipsoid;
2455  getOperationAndEllipsoidFromProjString( proj4, operation, ellipsoid );
2456  d->mProjectionAcronym = operation;
2457  d->mEllipsoidAcronym.clear();
2458  d->setPj( std::move( crs ) );
2459 
2460  const QString dbVals = sAuthIdToQgisSrsIdMap.value( QStringLiteral( "%1:%2" ).arg( auth, code ).toUpper() );
2461  QString srsId;
2462  QString srId;
2463  if ( !dbVals.isEmpty() )
2464  {
2465  const QStringList parts = dbVals.split( ',' );
2466  d->mSrsId = parts.at( 0 ).toInt();
2467  d->mSRID = parts.at( 1 ).toInt();
2468  }
2469 
2470  setMapUnits();
2471 
2472  return true;
2473 }
2474 
2475 QList<long> QgsCoordinateReferenceSystem::userSrsIds()
2476 {
2477  QList<long> results;
2478  // check user defined projection database
2479  const QString db = QgsApplication::qgisUserDatabaseFilePath();
2480 
2481  QFileInfo myInfo( db );
2482  if ( !myInfo.exists() )
2483  {
2484  QgsDebugMsg( "failed : " + db + " does not exist!" );
2485  return results;
2486  }
2487 
2488  sqlite3_database_unique_ptr database;
2489  sqlite3_statement_unique_ptr statement;
2490 
2491  //check the db is available
2492  int result = openDatabase( db, database );
2493  if ( result != SQLITE_OK )
2494  {
2495  QgsDebugMsg( "failed : " + db + " could not be opened!" );
2496  return results;
2497  }
2498 
2499  QString sql = QStringLiteral( "select srs_id from tbl_srs where srs_id >= %1" ).arg( USER_CRS_START_ID );
2500  int rc;
2501  statement = database.prepare( sql, rc );
2502  while ( true )
2503  {
2504  int ret = statement.step();
2505 
2506  if ( ret == SQLITE_DONE )
2507  {
2508  // there are no more rows to fetch - we can stop looping
2509  break;
2510  }
2511 
2512  if ( ret == SQLITE_ROW )
2513  {
2514  results.append( statement.columnAsInt64( 0 ) );
2515  }
2516  else
2517  {
2518  QgsMessageLog::logMessage( QObject::tr( "SQLite error: %2\nSQL: %1" ).arg( sql, sqlite3_errmsg( database.get() ) ), QObject::tr( "SpatiaLite" ) );
2519  break;
2520  }
2521  }
2522 
2523  return results;
2524 }
2525 
2526 long QgsCoordinateReferenceSystem::matchToUserCrs() const
2527 {
2528  PJ *obj = d->threadLocalProjObject();
2529  if ( !obj )
2530  return 0;
2531 
2532  const QList< long > ids = userSrsIds();
2533  for ( long id : ids )
2534  {
2536  if ( candidate.projObject() && proj_is_equivalent_to( obj, candidate.projObject(), PJ_COMP_EQUIVALENT ) )
2537  {
2538  return id;
2539  }
2540  }
2541  return 0;
2542 }
2543 #endif
2544 
2545 #if PROJ_VERSION_MAJOR<6
2546 // adapted from gdal/ogr/ogr_srs_dict.cpp
2547 bool QgsCoordinateReferenceSystem::loadWkts( QHash<int, QString> &wkts, const char *filename )
2548 {
2549  QgsDebugMsgLevel( QStringLiteral( "Loading %1" ).arg( filename ), 4 );
2550  const char *pszFilename = CPLFindFile( "gdal", filename );
2551  if ( !pszFilename )
2552  return false;
2553 
2554  QFile csv( pszFilename );
2555  if ( !csv.open( QIODevice::ReadOnly ) )
2556  return false;
2557 
2558  QTextStream lines( &csv );
2559 
2560  for ( ;; )
2561  {
2562  QString line = lines.readLine();
2563  if ( line.isNull() )
2564  break;
2565 
2566  if ( line.trimmed().isEmpty() || line.startsWith( '#' ) )
2567  {
2568  continue;
2569  }
2570  else if ( line.startsWith( QLatin1String( "include " ) ) )
2571  {
2572  if ( !loadWkts( wkts, line.mid( 8 ).toUtf8() ) )
2573  break;
2574  }
2575  else
2576  {
2577  int pos = line.indexOf( ',' );
2578  if ( pos < 0 )
2579  return false;
2580 
2581  bool ok;
2582  int epsg = line.leftRef( pos ).toInt( &ok );
2583  if ( !ok )
2584  return false;
2585 
2586  wkts.insert( epsg, line.mid( pos + 1 ) );
2587  }
2588  }
2589 
2590  csv.close();
2591 
2592  return true;
2593 }
2594 
2595 bool QgsCoordinateReferenceSystem::loadIds( QHash<int, QString> &wkts )
2596 {
2597  OGRSpatialReferenceH crs = OSRNewSpatialReference( nullptr );
2598 
2599  static const QStringList csvs { QStringList() << QStringLiteral( "gcs.csv" ) << QStringLiteral( "pcs.csv" ) << QStringLiteral( "vertcs.csv" ) << QStringLiteral( "compdcs.csv" ) << QStringLiteral( "geoccs.csv" ) };
2600  for ( const QString &csv : csvs )
2601  {
2602  QString filename = CPLFindFile( "gdal", csv.toUtf8() );
2603 
2604  QFile f( filename );
2605  if ( !f.open( QIODevice::ReadOnly ) )
2606  continue;
2607 
2608  QTextStream lines( &f );
2609  int l = 0, n = 0;
2610 
2611  lines.readLine();
2612  for ( ;; )
2613  {
2614  l++;
2615  QString line = lines.readLine();
2616  if ( line.isNull() )
2617  break;
2618 
2619  if ( line.trimmed().isEmpty() )
2620  continue;
2621 
2622  int pos = line.indexOf( ',' );
2623  if ( pos < 0 )
2624  {
2625  qWarning( "No id found in: %s", qPrintable( line ) );
2626  continue;
2627  }
2628 
2629  bool ok;
2630  int epsg = line.leftRef( pos ).toInt( &ok );
2631  if ( !ok )
2632  {
2633  qWarning( "No valid id found in: %s", qPrintable( line ) );
2634  continue;
2635  }
2636 
2637  // some CRS are known to fail (see http://trac.osgeo.org/gdal/ticket/2900)
2638  if ( epsg == 2218 || epsg == 2221 || epsg == 2296 || epsg == 2297 || epsg == 2298 || epsg == 2299 || epsg == 2300 || epsg == 2301 || epsg == 2302 ||
2639  epsg == 2303 || epsg == 2304 || epsg == 2305 || epsg == 2306 || epsg == 2307 || epsg == 2963 || epsg == 2985 || epsg == 2986 || epsg == 3052 ||
2640  epsg == 3053 || epsg == 3139 || epsg == 3144 || epsg == 3145 || epsg == 3173 || epsg == 3295 || epsg == 3993 || epsg == 4087 || epsg == 4088 ||
2641  epsg == 5017 || epsg == 5221 || epsg == 5224 || epsg == 5225 || epsg == 5514 || epsg == 5515 || epsg == 5516 || epsg == 5819 || epsg == 5820 ||
2642  epsg == 5821 || epsg == 6200 || epsg == 6201 || epsg == 6202 || epsg == 6244 || epsg == 6245 || epsg == 6246 || epsg == 6247 || epsg == 6248 ||
2643  epsg == 6249 || epsg == 6250 || epsg == 6251 || epsg == 6252 || epsg == 6253 || epsg == 6254 || epsg == 6255 || epsg == 6256 || epsg == 6257 ||
2644  epsg == 6258 || epsg == 6259 || epsg == 6260 || epsg == 6261 || epsg == 6262 || epsg == 6263 || epsg == 6264 || epsg == 6265 || epsg == 6266 ||
2645  epsg == 6267 || epsg == 6268 || epsg == 6269 || epsg == 6270 || epsg == 6271 || epsg == 6272 || epsg == 6273 || epsg == 6274 || epsg == 6275 ||
2646  epsg == 6966 || epsg == 7082 || epsg == 32600 || epsg == 32663 || epsg == 32700 )
2647  continue;
2648 
2649  if ( OSRImportFromEPSG( crs, epsg ) != OGRERR_NONE )
2650  {
2651  qDebug( "EPSG %d: not imported", epsg );
2652  continue;
2653  }
2654 
2655  char *wkt = nullptr;
2656  if ( OSRExportToWkt( crs, &wkt ) != OGRERR_NONE )
2657  {
2658  qWarning( "EPSG %d: not exported to WKT", epsg );
2659  continue;
2660  }
2661 
2662  wkts.insert( epsg, wkt );
2663  n++;
2664 
2665  CPLFree( wkt );
2666  }
2667 
2668  f.close();
2669 
2670  QgsDebugMsgLevel( QStringLiteral( "Loaded %1/%2 from %3" ).arg( QString::number( n ), QString::number( l ), filename.toUtf8().constData() ), 4 );
2671  }
2672 
2673  OSRDestroySpatialReference( crs );
2674 
2675  return true;
2676 }
2677 #endif
2678 
2679 #if PROJ_VERSION_MAJOR>=6
2680 static void sync_db_proj_logger( void * /* user_data */, int level, const char *message )
2681 {
2682 #ifndef QGISDEBUG
2683  Q_UNUSED( message )
2684 #endif
2685  if ( level == PJ_LOG_ERROR )
2686  {
2687  QgsDebugMsgLevel( QStringLiteral( "PROJ: %1" ).arg( message ), 2 );
2688  }
2689  else if ( level == PJ_LOG_DEBUG )
2690  {
2691  QgsDebugMsgLevel( QStringLiteral( "PROJ: %1" ).arg( message ), 3 );
2692  }
2693 }
2694 #endif
2695 
2697 {
2698  setlocale( LC_ALL, "C" );
2699  QString dbFilePath = QgsApplication::srsDatabaseFilePath();
2700 
2701 #if PROJ_VERSION_MAJOR<6
2702  syncDatumTransform( dbFilePath );
2703 #endif
2704 
2705  int inserted = 0, updated = 0, deleted = 0, errors = 0;
2706 
2707  QgsDebugMsgLevel( QStringLiteral( "Load srs db from: %1" ).arg( QgsApplication::srsDatabaseFilePath().toLocal8Bit().constData() ), 4 );
2708 
2709  sqlite3_database_unique_ptr database;
2710  if ( database.open( dbFilePath ) != SQLITE_OK )
2711  {
2712  QgsDebugMsg( QStringLiteral( "Could not open database: %1 (%2)\n" ).arg( QgsApplication::srsDatabaseFilePath(), database.errorMessage() ) );
2713  return -1;
2714  }
2715 
2716  if ( sqlite3_exec( database.get(), "BEGIN TRANSACTION", nullptr, nullptr, nullptr ) != SQLITE_OK )
2717  {
2718  QgsDebugMsg( QStringLiteral( "Could not begin transaction: %1 (%2)\n" ).arg( QgsApplication::srsDatabaseFilePath(), database.errorMessage() ) );
2719  return -1;
2720  }
2721 
2722  sqlite3_statement_unique_ptr statement;
2723  int result;
2724  char *errMsg = nullptr;
2725 
2726 #if PROJ_VERSION_MAJOR<6
2727 // fix up database, if not done already //
2728  if ( sqlite3_exec( database.get(), "alter table tbl_srs add noupdate boolean", nullptr, nullptr, nullptr ) == SQLITE_OK )
2729  ( void )sqlite3_exec( database.get(), "update tbl_srs set noupdate=(auth_name='EPSG' and auth_id in (5513,5514,5221,2065,102067,4156,4818))", nullptr, nullptr, nullptr );
2730 
2731  ( void )sqlite3_exec( database.get(), "UPDATE tbl_srs SET srid=141001 WHERE srid=41001 AND auth_name='OSGEO' AND auth_id='41001'", nullptr, nullptr, nullptr );
2732 #else
2733  if ( sqlite3_exec( database.get(), "create table tbl_info (proj_major INT, proj_minor INT, proj_patch INT)", nullptr, nullptr, nullptr ) == SQLITE_OK )
2734  {
2735  QString sql = QStringLiteral( "INSERT INTO tbl_info(proj_major, proj_minor, proj_patch) VALUES (%1, %2,%3)" )
2736  .arg( QString::number( PROJ_VERSION_MAJOR ),
2737  QString::number( PROJ_VERSION_MINOR ),
2738  QString::number( PROJ_VERSION_PATCH ) );
2739  if ( sqlite3_exec( database.get(), sql.toUtf8(), nullptr, nullptr, &errMsg ) != SQLITE_OK )
2740  {
2741  QgsDebugMsg( QStringLiteral( "Could not execute: %1 [%2/%3]\n" ).arg(
2742  sql,
2743  database.errorMessage(),
2744  errMsg ? errMsg : "(unknown error)" ) );
2745  if ( errMsg )
2746  sqlite3_free( errMsg );
2747  return -1;
2748  }
2749  }
2750  else
2751  {
2752  // retrieve last update details
2753  QString sql = QStringLiteral( "SELECT proj_major, proj_minor, proj_patch FROM tbl_info" );
2754  statement = database.prepare( sql, result );
2755  if ( result != SQLITE_OK )
2756  {
2757  QgsDebugMsg( QStringLiteral( "Could not prepare: %1 [%2]\n" ).arg( sql, database.errorMessage() ) );
2758  return -1;
2759  }
2760  if ( statement.step() == SQLITE_ROW )
2761  {
2762  int major = statement.columnAsInt64( 0 );
2763  int minor = statement.columnAsInt64( 1 );
2764  int patch = statement.columnAsInt64( 2 );
2765  if ( major == PROJ_VERSION_MAJOR && minor == PROJ_VERSION_MINOR && patch == PROJ_VERSION_PATCH )
2766  // yay, nothing to do!
2767  return 0;
2768  }
2769  else
2770  {
2771  QgsDebugMsg( QStringLiteral( "Could not retrieve previous CRS sync PROJ version number" ) );
2772  return -1;
2773  }
2774  }
2775 #endif
2776 
2777 
2778 #if PROJ_VERSION_MAJOR>=6
2779  PJ_CONTEXT *pjContext = QgsProjContext::get();
2780  // silence proj warnings
2781  proj_log_func( pjContext, nullptr, sync_db_proj_logger );
2782 
2783  PROJ_STRING_LIST authorities = proj_get_authorities_from_database( pjContext );
2784 
2785  int nextSrsId = 63321;
2786  int nextSrId = 520003321;
2787  for ( auto authIter = authorities; authIter && *authIter; ++authIter )
2788  {
2789  const QString authority( *authIter );
2790  QgsDebugMsgLevel( QStringLiteral( "Loading authority '%1'" ).arg( authority ), 2 );
2791  PROJ_STRING_LIST codes = proj_get_codes_from_database( pjContext, *authIter, PJ_TYPE_CRS, true );
2792 
2793  QStringList allCodes;
2794 
2795  for ( auto codesIter = codes; codesIter && *codesIter; ++codesIter )
2796  {
2797  const QString code( *codesIter );
2798  allCodes << QgsSqliteUtils::quotedString( code );
2799  QgsDebugMsgLevel( QStringLiteral( "Loading code '%1'" ).arg( code ), 4 );
2800  QgsProjUtils::proj_pj_unique_ptr crs( proj_create_from_database( pjContext, *authIter, *codesIter, PJ_CATEGORY_CRS, false, nullptr ) );
2801  if ( !crs )
2802  {
2803  QgsDebugMsg( QStringLiteral( "Could not load '%1:%2'" ).arg( authority, code ) );
2804  continue;
2805  }
2806 
2807  switch ( proj_get_type( crs.get() ) )
2808  {
2809  case PJ_TYPE_VERTICAL_CRS: // don't need these in the CRS db
2810  continue;
2811 
2812  default:
2813  break;
2814  }
2815 
2816  crs = QgsProjUtils::crsToSingleCrs( crs.get() );
2817 
2818  QString proj4 = getFullProjString( crs.get() );
2819  proj4.replace( QLatin1String( "+type=crs" ), QString() );
2820  proj4 = proj4.trimmed();
2821 
2822  if ( proj4.isEmpty() )
2823  {
2824  QgsDebugMsgLevel( QStringLiteral( "No proj4 for '%1:%2'" ).arg( authority, code ), 2 );
2825  // satisfy not null constraint
2826  proj4 = "";
2827  }
2828 
2829  const bool deprecated = proj_is_deprecated( crs.get() );
2830  const QString name( proj_get_name( crs.get() ) );
2831 
2832  QString sql = QStringLiteral( "SELECT parameters,description,deprecated FROM tbl_srs WHERE auth_name='%1' AND auth_id='%2'" ).arg( authority, code );
2833  statement = database.prepare( sql, result );
2834  if ( result != SQLITE_OK )
2835  {
2836  QgsDebugMsg( QStringLiteral( "Could not prepare: %1 [%2]\n" ).arg( sql, database.errorMessage() ) );
2837  continue;
2838  }
2839 
2840  QString srsProj4;
2841  QString srsDesc;
2842  bool srsDeprecated = deprecated;
2843  if ( statement.step() == SQLITE_ROW )
2844  {
2845  srsProj4 = statement.columnAsText( 0 );
2846  srsDesc = statement.columnAsText( 1 );
2847  srsDeprecated = statement.columnAsText( 2 ).toInt() != 0;
2848  }
2849 
2850  if ( !srsProj4.isEmpty() || !srsDesc.isEmpty() )
2851  {
2852  if ( proj4 != srsProj4 || name != srsDesc || deprecated != srsDeprecated )
2853  {
2854  errMsg = nullptr;
2855  sql = QStringLiteral( "UPDATE tbl_srs SET parameters=%1,description=%2,deprecated=%3 WHERE auth_name=%4 AND auth_id=%5" )
2856  .arg( QgsSqliteUtils::quotedString( proj4 ) )
2857  .arg( QgsSqliteUtils::quotedString( name ) )
2858  .arg( deprecated ? 1 : 0 )
2859  .arg( QgsSqliteUtils::quotedString( authority ), QgsSqliteUtils::quotedString( code ) );
2860 
2861  if ( sqlite3_exec( database.get(), sql.toUtf8(), nullptr, nullptr, &errMsg ) != SQLITE_OK )
2862  {
2863  QgsDebugMsg( QStringLiteral( "Could not execute: %1 [%2/%3]\n" ).arg(
2864  sql,
2865  database.errorMessage(),
2866  errMsg ? errMsg : "(unknown error)" ) );
2867  if ( errMsg )
2868  sqlite3_free( errMsg );
2869  errors++;
2870  }
2871  else
2872  {
2873  updated++;
2874  }
2875  }
2876  }
2877  else
2878  {
2879  // there's a not-null contraint on these columns, so we must use empty strings instead
2880  QString operation = "";
2881  QString ellps = "";
2882  getOperationAndEllipsoidFromProjString( proj4, operation, ellps );
2883  const bool isGeographic = testIsGeographic( crs.get() );
2884 
2885  // work out srid and srsid
2886  const QString dbVals = sAuthIdToQgisSrsIdMap.value( QStringLiteral( "%1:%2" ).arg( authority, code ) );
2887  QString srsId;
2888  QString srId;
2889  if ( !dbVals.isEmpty() )
2890  {
2891  const QStringList parts = dbVals.split( ',' );
2892  srsId = parts.at( 0 );
2893  srId = parts.at( 1 );
2894  }
2895  if ( srId.isEmpty() )
2896  {
2897  srId = QString::number( nextSrId );
2898  nextSrId++;
2899  }
2900  if ( srsId.isEmpty() )
2901  {
2902  srsId = QString::number( nextSrsId );
2903  nextSrsId++;
2904  }
2905 
2906  if ( !srsId.isEmpty() )
2907  {
2908  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)" )
2909  .arg( srsId )
2910  .arg( QgsSqliteUtils::quotedString( name ),
2911  QgsSqliteUtils::quotedString( operation ),
2913  QgsSqliteUtils::quotedString( proj4 ) )
2914  .arg( srId )
2915  .arg( QgsSqliteUtils::quotedString( authority ) )
2916  .arg( QgsSqliteUtils::quotedString( code ) )
2917  .arg( isGeographic ? 1 : 0 )
2918  .arg( deprecated ? 1 : 0 );
2919  }
2920  else
2921  {
2922  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)" )
2923  .arg( QgsSqliteUtils::quotedString( name ),
2924  QgsSqliteUtils::quotedString( operation ),
2926  QgsSqliteUtils::quotedString( proj4 ) )
2927  .arg( srId )
2928  .arg( QgsSqliteUtils::quotedString( authority ) )
2929  .arg( QgsSqliteUtils::quotedString( code ) )
2930  .arg( isGeographic ? 1 : 0 )
2931  .arg( deprecated ? 1 : 0 );
2932  }
2933 
2934  errMsg = nullptr;
2935  if ( sqlite3_exec( database.get(), sql.toUtf8(), nullptr, nullptr, &errMsg ) == SQLITE_OK )
2936  {
2937  inserted++;
2938  }
2939  else
2940  {
2941  qCritical( "Could not execute: %s [%s/%s]\n",
2942  sql.toLocal8Bit().constData(),
2943  sqlite3_errmsg( database.get() ),
2944  errMsg ? errMsg : "(unknown error)" );
2945  errors++;
2946 
2947  if ( errMsg )
2948  sqlite3_free( errMsg );
2949  }
2950  }
2951  }
2952 
2953  proj_string_list_destroy( codes );
2954 
2955  const QString sql = QStringLiteral( "DELETE FROM tbl_srs WHERE auth_name='%1' AND NOT auth_id IN (%2)" ).arg( authority, allCodes.join( ',' ) );
2956  if ( sqlite3_exec( database.get(), sql.toUtf8(), nullptr, nullptr, nullptr ) == SQLITE_OK )
2957  {
2958  deleted = sqlite3_changes( database.get() );
2959  }
2960  else
2961  {
2962  errors++;
2963  qCritical( "Could not execute: %s [%s]\n",
2964  sql.toLocal8Bit().constData(),
2965  sqlite3_errmsg( database.get() ) );
2966  }
2967 
2968  }
2969  proj_string_list_destroy( authorities );
2970 
2971 #else
2972 
2973  OGRSpatialReferenceH crs = nullptr;
2974 
2975  QString proj4;
2976  QString sql;
2977  QHash<int, QString> wkts;
2978  loadIds( wkts );
2979  loadWkts( wkts, "epsg.wkt" );
2980 
2981  QgsDebugMsgLevel( QStringLiteral( "%1 WKTs loaded" ).arg( wkts.count() ), 4 );
2982 
2983  for ( QHash<int, QString>::const_iterator it = wkts.constBegin(); it != wkts.constEnd(); ++it )
2984  {
2985  QByteArray ba( it.value().toUtf8() );
2986  char *psz = ba.data();
2987 
2988  if ( crs )
2989  OSRDestroySpatialReference( crs );
2990  crs = nullptr;
2991  crs = OSRNewSpatialReference( nullptr );
2992 
2993  OGRErr ogrErr = OSRImportFromWkt( crs, &psz );
2994  if ( ogrErr != OGRERR_NONE )
2995  continue;
2996 
2997  if ( OSRExportToProj4( crs, &psz ) != OGRERR_NONE )
2998  {
2999  CPLFree( psz );
3000  continue;
3001  }
3002 
3003  proj4 = psz;
3004  proj4 = proj4.trimmed();
3005 
3006  CPLFree( psz );
3007 
3008  if ( proj4.isEmpty() )
3009  continue;
3010 
3011  QString name( OSRIsGeographic( crs ) ? OSRGetAttrValue( crs, "GEOGCS", 0 ) :
3012  OSRIsGeocentric( crs ) ? OSRGetAttrValue( crs, "GEOCCS", 0 ) :
3013  OSRGetAttrValue( crs, "PROJCS", 0 ) );
3014  if ( name.isEmpty() )
3015  name = QObject::tr( "Imported from GDAL" );
3016 
3017  bool deprecated = name.contains( QLatin1String( "(deprecated)" ) );
3018 
3019  sql = QStringLiteral( "SELECT parameters,description,deprecated,noupdate FROM tbl_srs WHERE auth_name='EPSG' AND auth_id='%1'" ).arg( it.key() );
3020  statement = database.prepare( sql, result );
3021  if ( result != SQLITE_OK )
3022  {
3023  QgsDebugMsg( QStringLiteral( "Could not prepare: %1 [%2]\n" ).arg( sql, database.errorMessage() ) );
3024  continue;
3025  }
3026 
3027  QString srsProj4;
3028  QString srsDesc;
3029  bool srsDeprecated = deprecated;
3030  if ( statement.step() == SQLITE_ROW )
3031  {
3032  srsProj4 = statement.columnAsText( 0 );
3033  srsDesc = statement.columnAsText( 1 );
3034  srsDeprecated = statement.columnAsText( 2 ).toInt() != 0;
3035 
3036  if ( statement.columnAsText( 3 ).toInt() != 0 )
3037  {
3038  continue;
3039  }
3040  }
3041 
3042  if ( !srsProj4.isEmpty() || !srsDesc.isEmpty() )
3043  {
3044  if ( proj4 != srsProj4 || name != srsDesc || deprecated != srsDeprecated )
3045  {
3046  errMsg = nullptr;
3047  sql = QStringLiteral( "UPDATE tbl_srs SET parameters=%1,description=%2,deprecated=%3 WHERE auth_name='EPSG' AND auth_id=%4" )
3048  .arg( QgsSqliteUtils::quotedString( proj4 ) )
3049  .arg( QgsSqliteUtils::quotedString( name ) )
3050  .arg( deprecated ? 1 : 0 )
3051  .arg( it.key() );
3052 
3053  if ( sqlite3_exec( database.get(), sql.toUtf8(), nullptr, nullptr, &errMsg ) != SQLITE_OK )
3054  {
3055  QgsDebugMsg( QStringLiteral( "Could not execute: %1 [%2/%3]\n" ).arg(
3056  sql,
3057  database.errorMessage(),
3058  errMsg ? errMsg : "(unknown error)" ) );
3059  if ( errMsg )
3060  sqlite3_free( errMsg );
3061  errors++;
3062  }
3063  else
3064  {
3065  updated++;
3066  }
3067  }
3068  }
3069  else
3070  {
3071  QRegExp projRegExp( "\\+proj=(\\S+)" );
3072  if ( projRegExp.indexIn( proj4 ) < 0 )
3073  {
3074  QgsDebugMsgLevel( QStringLiteral( "EPSG %1: no +proj argument found [%2]" ).arg( it.key() ).arg( proj4 ), 4 );
3075  continue;
3076  }
3077 
3078  QRegExp ellipseRegExp( "\\+ellps=(\\S+)" );
3079  QString ellps;
3080  if ( ellipseRegExp.indexIn( proj4 ) >= 0 )
3081  {
3082  ellps = ellipseRegExp.cap( 1 );
3083  }
3084  else
3085  {
3086  // satisfy not null constraint on ellipsoid_acronym field
3087  // possibly we should drop the constraint, yet the crses with missing ellipsoid_acronym are malformed
3088  // and will result in oddities within other areas of QGIS (e.g. project ellipsoid won't be correctly
3089  // set for these CRSes). Better just hack around and make the constraint happy for now,
3090  // and hope that the definitions get corrected in future.
3091  ellps = "";
3092  }
3093 
3094  sql = QStringLiteral( "INSERT INTO tbl_srs(description,projection_acronym,ellipsoid_acronym,parameters,srid,auth_name,auth_id,is_geo,deprecated) VALUES (%1,%2,%3,%4,%5,'EPSG',%5,%6,%7)" )
3095  .arg( QgsSqliteUtils::quotedString( name ),
3096  QgsSqliteUtils::quotedString( projRegExp.cap( 1 ) ),
3098  QgsSqliteUtils::quotedString( proj4 ) )
3099  .arg( it.key() )
3100  .arg( OSRIsGeographic( crs ) )
3101  .arg( deprecated ? 1 : 0 );
3102 
3103  errMsg = nullptr;
3104  if ( sqlite3_exec( database.get(), sql.toUtf8(), nullptr, nullptr, &errMsg ) == SQLITE_OK )
3105  {
3106  inserted++;
3107  }
3108  else
3109  {
3110  qCritical( "Could not execute: %s [%s/%s]\n",
3111  sql.toLocal8Bit().constData(),
3112  sqlite3_errmsg( database.get() ),
3113  errMsg ? errMsg : "(unknown error)" );
3114  errors++;
3115 
3116  if ( errMsg )
3117  sqlite3_free( errMsg );
3118  }
3119  }
3120  }
3121 
3122  if ( crs )
3123  OSRDestroySpatialReference( crs );
3124  crs = nullptr;
3125 
3126  sql = QStringLiteral( "DELETE FROM tbl_srs WHERE auth_name='EPSG' AND NOT auth_id IN (" );
3127  QString delim;
3128  QHash<int, QString>::const_iterator it = wkts.constBegin();
3129  for ( ; it != wkts.constEnd(); ++it )
3130  {
3131  sql += delim + QString::number( it.key() );
3132  delim = ',';
3133  }
3134  sql += QLatin1String( ") AND NOT noupdate" );
3135 
3136  if ( sqlite3_exec( database.get(), sql.toUtf8(), nullptr, nullptr, nullptr ) == SQLITE_OK )
3137  {
3138  deleted = sqlite3_changes( database.get() );
3139  }
3140  else
3141  {
3142  errors++;
3143  qCritical( "Could not execute: %s [%s]\n",
3144  sql.toLocal8Bit().constData(),
3145  sqlite3_errmsg( database.get() ) );
3146  }
3147 
3148  projCtx pContext = pj_ctx_alloc();
3149 
3150 #if !defined(PJ_VERSION) || PJ_VERSION!=470
3151  sql = QStringLiteral( "select auth_name,auth_id,parameters from tbl_srs WHERE auth_name<>'EPSG' AND NOT deprecated AND NOT noupdate" );
3152  statement = database.prepare( sql, result );
3153  if ( result == SQLITE_OK )
3154  {
3155  while ( statement.step() == SQLITE_ROW )
3156  {
3157  QString auth_name = statement.columnAsText( 0 );
3158  QString auth_id = statement.columnAsText( 1 );
3159  QString params = statement.columnAsText( 2 );
3160 
3161  QString input = QStringLiteral( "+init=%1:%2" ).arg( auth_name.toLower(), auth_id );
3162  projPJ pj = pj_init_plus_ctx( pContext, input.toLatin1() );
3163  if ( !pj )
3164  {
3165  input = QStringLiteral( "+init=%1:%2" ).arg( auth_name.toUpper(), auth_id );
3166  pj = pj_init_plus_ctx( pContext, input.toLatin1() );
3167  }
3168 
3169  if ( pj )
3170  {
3171  char *def = pj_get_def( pj, 0 );
3172  if ( def )
3173  {
3174  proj4 = def;
3175  pj_dalloc( def );
3176 
3177  input.prepend( ' ' ).append( ' ' );
3178  if ( proj4.startsWith( input ) )
3179  {
3180  proj4 = proj4.mid( input.size() );
3181  proj4 = proj4.trimmed();
3182  }
3183 
3184  if ( proj4 != params )
3185  {
3186  sql = QStringLiteral( "UPDATE tbl_srs SET parameters=%1 WHERE auth_name=%2 AND auth_id=%3" )
3187  .arg( QgsSqliteUtils::quotedString( proj4 ),
3188  QgsSqliteUtils::quotedString( auth_name ),
3189  QgsSqliteUtils::quotedString( auth_id ) );
3190 
3191  if ( sqlite3_exec( database.get(), sql.toUtf8(), nullptr, nullptr, &errMsg ) == SQLITE_OK )
3192  {
3193  updated++;
3194  }
3195  else
3196  {
3197  qCritical( "Could not execute: %s [%s/%s]\n",
3198  sql.toLocal8Bit().constData(),
3199  sqlite3_errmsg( database.get() ),
3200  errMsg ? errMsg : "(unknown error)" );
3201  if ( errMsg )
3202  sqlite3_free( errMsg );
3203  errors++;
3204  }
3205  }
3206  }
3207  else
3208  {
3209  QgsDebugMsgLevel( QStringLiteral( "could not retrieve proj string for %1 from PROJ" ).arg( input ), 4 );
3210  }
3211  }
3212  else
3213  {
3214  QgsDebugMsgLevel( QStringLiteral( "could not retrieve crs for %1 from PROJ" ).arg( input ), 3 );
3215  }
3216 
3217  pj_free( pj );
3218  }
3219  }
3220  else
3221  {
3222  errors++;
3223  QgsDebugMsg( QStringLiteral( "Could not execute: %1 [%2]\n" ).arg(
3224  sql,
3225  sqlite3_errmsg( database.get() ) ) );
3226  }
3227 #endif
3228 
3229  pj_ctx_free( pContext );
3230 
3231 #endif
3232 
3233 #if PROJ_VERSION_MAJOR>=6
3234  QString sql = QStringLiteral( "UPDATE tbl_info set proj_major=%1,proj_minor=%2,proj_patch=%3" )
3235  .arg( QString::number( PROJ_VERSION_MAJOR ),
3236  QString::number( PROJ_VERSION_MINOR ),
3237  QString::number( PROJ_VERSION_PATCH ) );
3238  if ( sqlite3_exec( database.get(), sql.toUtf8(), nullptr, nullptr, &errMsg ) != SQLITE_OK )
3239  {
3240  QgsDebugMsg( QStringLiteral( "Could not execute: %1 [%2/%3]\n" ).arg(
3241  sql,
3242  database.errorMessage(),
3243  errMsg ? errMsg : "(unknown error)" ) );
3244  if ( errMsg )
3245  sqlite3_free( errMsg );
3246  return -1;
3247  }
3248 #endif
3249 
3250  if ( sqlite3_exec( database.get(), "COMMIT", nullptr, nullptr, nullptr ) != SQLITE_OK )
3251  {
3252  QgsDebugMsg( QStringLiteral( "Could not commit transaction: %1 [%2]\n" ).arg(
3254  sqlite3_errmsg( database.get() ) )
3255  );
3256  return -1;
3257  }
3258 
3259 #ifdef QGISDEBUG
3260  QgsDebugMsgLevel( QStringLiteral( "CRS update (inserted:%1 updated:%2 deleted:%3 errors:%4)" ).arg( inserted ).arg( updated ).arg( deleted ).arg( errors ), 4 );
3261 #else
3262  Q_UNUSED( deleted )
3263 #endif
3264 
3265  if ( errors > 0 )
3266  return -errors;
3267  else
3268  return updated + inserted;
3269 }
3270 
3271 #if PROJ_VERSION_MAJOR<6
3272 bool QgsCoordinateReferenceSystem::syncDatumTransform( const QString &dbPath )
3273 {
3274  const char *filename = CSVFilename( "datum_shift.csv" );
3275  FILE *fp = VSIFOpen( filename, "rb" );
3276  if ( !fp )
3277  {
3278  return false;
3279  }
3280 
3281  char **fieldnames = CSVReadParseLine( fp );
3282 
3283  // "SEQ_KEY","COORD_OP_CODE","SOURCE_CRS_CODE","TARGET_CRS_CODE","REMARKS","COORD_OP_SCOPE","AREA_OF_USE_CODE","AREA_SOUTH_BOUND_LAT","AREA_NORTH_BOUND_LAT","AREA_WEST_BOUND_LON","AREA_EAST_BOUND_LON","SHOW_OPERATION","DEPRECATED","COORD_OP_METHOD_CODE","DX","DY","DZ","RX","RY","RZ","DS","PREFERRED"
3284 
3285  struct
3286  {
3287  const char *src; //skip-init-check
3288  const char *dst; //skip-init-check
3289  int idx;
3290  } map[] =
3291  {
3292  // { "SEQ_KEY", "", -1 },
3293  { "SOURCE_CRS_CODE", "source_crs_code", -1 },
3294  { "TARGET_CRS_CODE", "target_crs_code", -1 },
3295  { "REMARKS", "remarks", -1 },
3296  { "COORD_OP_SCOPE", "scope", -1 },
3297  { "AREA_OF_USE_CODE", "area_of_use_code", -1 },
3298  // { "AREA_SOUTH_BOUND_LAT", "", -1 },
3299  // { "AREA_NORTH_BOUND_LAT", "", -1 },
3300  // { "AREA_WEST_BOUND_LON", "", -1 },
3301  // { "AREA_EAST_BOUND_LON", "", -1 },
3302  // { "SHOW_OPERATION", "", -1 },
3303  { "DEPRECATED", "deprecated", -1 },
3304  { "COORD_OP_METHOD_CODE", "coord_op_method_code", -1 },
3305  { "DX", "p1", -1 },
3306  { "DY", "p2", -1 },
3307  { "DZ", "p3", -1 },
3308  { "RX", "p4", -1 },
3309  { "RY", "p5", -1 },
3310  { "RZ", "p6", -1 },
3311  { "DS", "p7", -1 },
3312  { "PREFERRED", "preferred", -1 },
3313  { "COORD_OP_CODE", "coord_op_code", -1 },
3314  };
3315 
3316  QString update = QStringLiteral( "UPDATE tbl_datum_transform SET " );
3317  QString insert;
3318  const int n = CSLCount( fieldnames );
3319  int idxid = -1, idxrx = -1, idxry = -1, idxrz = -1, idxmcode = -1;
3320 
3321  {
3322  QString values;
3323 
3324  for ( unsigned int i = 0; i < sizeof( map ) / sizeof( *map ); i++ )
3325  {
3326  bool last = i == sizeof( map ) / sizeof( *map ) - 1;
3327 
3328  map[i].idx = CSLFindString( fieldnames, map[i].src );
3329  if ( map[i].idx < 0 )
3330  {
3331  qWarning( "field %s not found", map[i].src );
3332  CSLDestroy( fieldnames );
3333  fclose( fp );
3334  return false;
3335  }
3336 
3337  if ( strcmp( map[i].src, "COORD_OP_CODE" ) == 0 )
3338  idxid = i;
3339  if ( strcmp( map[i].src, "RX" ) == 0 )
3340  idxrx = i;
3341  if ( strcmp( map[i].src, "RY" ) == 0 )
3342  idxry = i;
3343  if ( strcmp( map[i].src, "RZ" ) == 0 )
3344  idxrz = i;
3345  if ( strcmp( map[i].src, "COORD_OP_METHOD_CODE" ) == 0 )
3346  idxmcode = i;
3347 
3348  if ( i > 0 )
3349  {
3350  insert += ',';
3351  values += ',';
3352 
3353  if ( last )
3354  {
3355  update += QLatin1String( " WHERE " );
3356  }
3357  else
3358  {
3359  update += ',';
3360  }
3361  }
3362 
3363  update += QStringLiteral( "%1=%%2" ).arg( map[i].dst ).arg( i + 1 );
3364 
3365  insert += map[i].dst;
3366  values += QStringLiteral( "%%1" ).arg( i + 1 );
3367  }
3368 
3369  insert = "INSERT INTO tbl_datum_transform(" + insert + ") VALUES (" + values + ')';
3370 
3371  Q_ASSERT( idxid >= 0 );
3372  Q_ASSERT( idxrx >= 0 );
3373  Q_ASSERT( idxry >= 0 );
3374  Q_ASSERT( idxrz >= 0 );
3375  }
3376 
3377  CSLDestroy( fieldnames );
3378 
3379  sqlite3_database_unique_ptr database;
3380  int openResult = database.open( dbPath );
3381  if ( openResult != SQLITE_OK )
3382  {
3383  fclose( fp );
3384  return false;
3385  }
3386 
3387  if ( sqlite3_exec( database.get(), "BEGIN TRANSACTION", nullptr, nullptr, nullptr ) != SQLITE_OK )
3388  {
3389  qCritical( "Could not begin transaction: %s [%s]\n", QgsApplication::srsDatabaseFilePath().toLocal8Bit().constData(), sqlite3_errmsg( database.get() ) );
3390  fclose( fp );
3391  return false;
3392  }
3393 
3394  QStringList v;
3395  v.reserve( sizeof( map ) / sizeof( *map ) );
3396 
3397  for ( ;; )
3398  {
3399  char **values = CSVReadParseLine( fp );
3400  if ( !values )
3401  break;
3402 
3403  v.clear();
3404 
3405  if ( CSLCount( values ) == 0 )
3406  {
3407  CSLDestroy( values );
3408  break;
3409  }
3410 
3411  if ( CSLCount( values ) < n )
3412  {
3413  qWarning( "Only %d columns", CSLCount( values ) );
3414  CSLDestroy( values );
3415  continue;
3416  }
3417 
3418  for ( unsigned int i = 0; i < sizeof( map ) / sizeof( *map ); i++ )
3419  {
3420  int idx = map[i].idx;
3421  Q_ASSERT( idx != -1 );
3422  Q_ASSERT( idx < n );
3423  v.insert( i, *values[ idx ] ? QgsSqliteUtils::quotedString( values[idx] ) : QStringLiteral( "NULL" ) );
3424  }
3425  CSLDestroy( values );
3426 
3427  //switch sign of rotation parameters. See http://trac.osgeo.org/proj/wiki/GenParms#towgs84-DatumtransformationtoWGS84
3428  if ( v.at( idxmcode ).compare( QLatin1String( "'9607'" ) ) == 0 )
3429  {
3430  v[ idxmcode ] = QStringLiteral( "'9606'" );
3431  v[ idxrx ] = '\'' + qgsDoubleToString( -( v[ idxrx ].remove( '\'' ).toDouble() ) ) + '\'';
3432  v[ idxry ] = '\'' + qgsDoubleToString( -( v[ idxry ].remove( '\'' ).toDouble() ) ) + '\'';
3433  v[ idxrz ] = '\'' + qgsDoubleToString( -( v[ idxrz ].remove( '\'' ).toDouble() ) ) + '\'';
3434  }
3435 
3436  //entry already in db?
3437  sqlite3_statement_unique_ptr statement;
3438  QString cOpCode;
3439  QString sql = QStringLiteral( "SELECT coord_op_code FROM tbl_datum_transform WHERE coord_op_code=%1" ).arg( v[ idxid ] );
3440  int prepareRes;
3441  statement = database.prepare( sql, prepareRes );
3442  if ( prepareRes != SQLITE_OK )
3443  continue;
3444 
3445  if ( statement.step() == SQLITE_ROW )
3446  {
3447  cOpCode = statement.columnAsText( 0 );
3448  }
3449 
3450  sql = cOpCode.isEmpty() ? insert : update;
3451  for ( int i = 0; i < v.size(); i++ )
3452  {
3453  sql = sql.arg( v[i] );
3454  }
3455 
3456  if ( sqlite3_exec( database.get(), sql.toUtf8(), nullptr, nullptr, nullptr ) != SQLITE_OK )
3457  {
3458  qCritical( "SQL: %s", sql.toUtf8().constData() );
3459  qCritical( "Error: %s", sqlite3_errmsg( database.get() ) );
3460  }
3461  }
3462 
3463  if ( sqlite3_exec( database.get(), "COMMIT", nullptr, nullptr, nullptr ) != SQLITE_OK )
3464  {
3465  QgsDebugMsg( QStringLiteral( "Could not commit transaction: %1 [%2]\n" ).arg( QgsApplication::srsDatabaseFilePath(), sqlite3_errmsg( database.get() ) ) );
3466  return false;
3467  }
3468 
3469  return true;
3470 }
3471 #endif
3472 
3473 const QHash<QString, QgsCoordinateReferenceSystem> &QgsCoordinateReferenceSystem::stringCache()
3474 {
3475  return *sStringCache();
3476 }
3477 
3478 const QHash<QString, QgsCoordinateReferenceSystem> &QgsCoordinateReferenceSystem::projCache()
3479 {
3480  return *sProj4Cache();
3481 }
3482 
3483 const QHash<QString, QgsCoordinateReferenceSystem> &QgsCoordinateReferenceSystem::ogcCache()
3484 {
3485  return *sOgcCache();
3486 }
3487 
3488 const QHash<QString, QgsCoordinateReferenceSystem> &QgsCoordinateReferenceSystem::wktCache()
3489 {
3490  return *sWktCache();
3491 }
3492 
3493 const QHash<long, QgsCoordinateReferenceSystem> &QgsCoordinateReferenceSystem::srIdCache()
3494 {
3495  return *sSrIdCache();
3496 }
3497 
3498 const QHash<long, QgsCoordinateReferenceSystem> &QgsCoordinateReferenceSystem::srsIdCache()
3499 {
3500  return *sSrsIdCache();
3501 }
3502 
3504 {
3505  if ( isGeographic() )
3506  {
3507  return d->mAuthId;
3508  }
3509 #if PROJ_VERSION_MAJOR>=6
3510  else if ( PJ *obj = d->threadLocalProjObject() )
3511  {
3512  QgsProjUtils::proj_pj_unique_ptr geoCrs( proj_crs_get_geodetic_crs( QgsProjContext::get(), obj ) );
3513  return geoCrs ? QStringLiteral( "%1:%2" ).arg( proj_get_id_auth_name( geoCrs.get(), 0 ), proj_get_id_code( geoCrs.get(), 0 ) ) : QString();
3514  }
3515 #else
3516  else if ( d->mCRS )
3517  {
3518  return OSRGetAuthorityName( d->mCRS, "GEOGCS" ) + QStringLiteral( ":" ) + OSRGetAuthorityCode( d->mCRS, "GEOGCS" );
3519  }
3520 #endif
3521  else
3522  {
3523  return QString();
3524  }
3525 }
3526 
3527 #if PROJ_VERSION_MAJOR>=6
3528 PJ *QgsCoordinateReferenceSystem::projObject() const
3529 {
3530  return d->threadLocalProjObject();
3531 }
3532 #endif
3533 
3535 {
3536  QStringList projections;
3537  const QList<QgsCoordinateReferenceSystem> res = recentCoordinateReferenceSystems();
3538  projections.reserve( res.size() );
3539  for ( const QgsCoordinateReferenceSystem &crs : res )
3540  {
3541  projections << QString::number( crs.srsid() );
3542  }
3543  return projections;
3544 }
3545 
3547 {
3548  QList<QgsCoordinateReferenceSystem> res;
3549 
3550  // Read settings from persistent storage
3551  QgsSettings settings;
3552  QStringList projectionsProj4 = settings.value( QStringLiteral( "UI/recentProjectionsProj4" ) ).toStringList();
3553  QStringList projectionsWkt = settings.value( QStringLiteral( "UI/recentProjectionsWkt" ) ).toStringList();
3554  QStringList projectionsAuthId = settings.value( QStringLiteral( "UI/recentProjectionsAuthId" ) ).toStringList();
3555  int max = std::max( projectionsAuthId.size(), std::max( projectionsProj4.size(), projectionsWkt.size() ) );
3556  res.reserve( max );
3557  for ( int i = 0; i < max; ++i )
3558  {
3559  const QString proj = projectionsProj4.value( i );
3560  const QString wkt = projectionsWkt.value( i );
3561  const QString authid = projectionsAuthId.value( i );
3562 
3564  if ( !authid.isEmpty() )
3566  if ( !crs.isValid() && !wkt.isEmpty() )
3567  crs.createFromWkt( wkt );
3568  if ( !crs.isValid() && !proj.isEmpty() )
3569  crs.createFromProj( wkt );
3570 
3571  if ( crs.isValid() )
3572  res << crs;
3573  }
3574  return res;
3575 }
3576 
3578 {
3579  // we only want saved and standard CRSes in the recent list
3580  if ( crs.srsid() == 0 || !crs.isValid() )
3581  return;
3582 
3583  QList<QgsCoordinateReferenceSystem> recent = recentCoordinateReferenceSystems();
3584  recent.removeAll( crs );
3585  recent.insert( 0, crs );
3586 
3587  // trim to max 30 items
3588  recent = recent.mid( 0, 30 );
3589  QStringList authids;
3590  authids.reserve( recent.size() );
3591  QStringList proj;
3592  proj.reserve( recent.size() );
3593  QStringList wkt;
3594  wkt.reserve( recent.size() );
3595  for ( const QgsCoordinateReferenceSystem &c : qgis::as_const( recent ) )
3596  {
3597  authids << c.authid();
3598  proj << c.toProj();
3599  wkt << c.toWkt( WKT_PREFERRED );
3600  }
3601 
3602  QgsSettings settings;
3603  settings.setValue( QStringLiteral( "UI/recentProjectionsAuthId" ), authids );
3604  settings.setValue( QStringLiteral( "UI/recentProjectionsWkt" ), wkt );
3605  settings.setValue( QStringLiteral( "UI/recentProjectionsProj4" ), proj );
3606 }
3607 
3609 {
3610  sSrIdCacheLock()->lockForWrite();
3611  if ( !sDisableSrIdCache )
3612  {
3613  if ( disableCache )
3614  sDisableSrIdCache = true;
3615  sSrIdCache()->clear();
3616  }
3617  sSrIdCacheLock()->unlock();
3618 
3619  sOgcLock()->lockForWrite();
3620  if ( !sDisableOgcCache )
3621  {
3622  if ( disableCache )
3623  sDisableOgcCache = true;
3624  sOgcCache()->clear();
3625  }
3626  sOgcLock()->unlock();
3627 
3628  sProj4CacheLock()->lockForWrite();
3629  if ( !sDisableProjCache )
3630  {
3631  if ( disableCache )
3632  sDisableProjCache = true;
3633  sProj4Cache()->clear();
3634  }
3635  sProj4CacheLock()->unlock();
3636 
3637  sCRSWktLock()->lockForWrite();
3638  if ( !sDisableWktCache )
3639  {
3640  if ( disableCache )
3641  sDisableWktCache = true;
3642  sWktCache()->clear();
3643  }
3644  sCRSWktLock()->unlock();
3645 
3646  sCRSSrsIdLock()->lockForWrite();
3647  if ( !sDisableSrsIdCache )
3648  {
3649  if ( disableCache )
3650  sDisableSrsIdCache = true;
3651  sSrsIdCache()->clear();
3652  }
3653  sCRSSrsIdLock()->unlock();
3654 
3655  sCrsStringLock()->lockForWrite();
3656  if ( !sDisableStringCache )
3657  {
3658  if ( disableCache )
3659  sDisableStringCache = true;
3660  sStringCache()->clear();
3661  }
3662  sCrsStringLock()->unlock();
3663 }
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.
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.
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.
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.
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.
QString userFriendlyIdentifier(IdentifierType type=MediumString) const
Returns a user friendly identifier for the CRS.
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.
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.
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.
static Q_DECL_DEPRECATED QgsCoordinateReferenceSystem fromProj4(const QString &proj4)
Creates a CRS from a proj style formatted string.
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::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
static QString OGRSpatialReferenceToWkt(OGRSpatialReferenceH srs)
Returns a WKT string corresponding to the specified OGR srs object.
static PJ_CONTEXT * get()
Returns a thread local instance of a proj context, safe for use in the current thread.
@ FlagMatchBoundCrsToUnderlyingSourceCrs
Allow matching a BoundCRS object to its underlying SourceCRS.
Definition: qgsprojutils.h:67
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:140
void setXMaximum(double x) SIP_HOLDGIL
Set the maximum x value.
Definition: qgsrectangle.h:135
void setXMinimum(double x) SIP_HOLDGIL
Set the minimum x value.
Definition: qgsrectangle.h:130
void setYMaximum(double y) SIP_HOLDGIL
Set the maximum y value.
Definition: qgsrectangle.h:145
This class is a composition of two QSettings instances:
Definition: qgssettings.h:62
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
static QString quotedString(const QString &value)
Returns a quoted string value, surround by ' characters and with special characters correctly escaped...
DistanceUnit
Units of distance.
Definition: qgsunittypes.h:68
@ DistanceMeters
Meters.
Definition: qgsunittypes.h:69
@ DistanceDegrees
Degrees, for planar geographic CRS distance measurements.
Definition: qgsunittypes.h:75
@ DistanceKilometers
Kilometers.
Definition: qgsunittypes.h:70
@ DistanceMiles
Terrestrial miles.
Definition: qgsunittypes.h:74
@ DistanceUnknownUnit
Unknown distance unit.
Definition: qgsunittypes.h:78
@ DistanceMillimeters
Millimeters.
Definition: qgsunittypes.h:77
@ DistanceYards
Imperial yards.
Definition: qgsunittypes.h:73
@ DistanceFeet
Imperial feet.
Definition: qgsunittypes.h:71
@ DistanceNauticalMiles
Nautical miles.
Definition: qgsunittypes.h:72
@ DistanceCentimeters
Centimeters.
Definition: qgsunittypes.h:76
Unique pointer for sqlite3 databases, which automatically closes the database when the pointer goes o...
sqlite3_statement_unique_ptr prepare(const QString &sql, int &resultCode) const
Prepares a sql statement, returning the result.
int open(const QString &path)
Opens the database at the specified file path.
QString errorMessage() const
Returns the most recent error message encountered by the database.
int open_v2(const QString &path, int flags, const char *zVfs)
Opens the database at the specified file path.
Unique pointer for sqlite3 prepared statements, which automatically finalizes the statement when the ...
QString columnAsText(int column) const
Returns the column value from the current statement row as a string.
QString columnName(int column) const
Returns the name of column.
double columnAsDouble(int column) const
Gets column value from the current statement row as a double.
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:798
QString qgsDoubleToString(double a, int precision=17)
Returns a string representation of a double.
Definition: qgis.h:276
#define Q_NOWARN_DEPRECATED_PUSH
Definition: qgis.h:797
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:316
const int USER_CRS_START_ID
Magick number that determines whether a projection crsid is a system (srs.db) or user (~/....
Definition: qgis.h:746
QHash< QString, QgsCoordinateReferenceSystem > StringCrsCacheHash
QHash< long, QgsCoordinateReferenceSystem > SrIdCrsCacheHash
const int LAT_PREFIX_LEN
The length of the string "+lat_1=".
void * OGRSpatialReferenceH
void(* CUSTOM_CRS_VALIDATION)(QgsCoordinateReferenceSystem &)
Q_GLOBAL_STATIC(QReadWriteLock, sDefinitionCacheLock)
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
void PJ_CONTEXT
Definition: qgsprojutils.h:151
#define FEET_TO_METER
const QgsCoordinateReferenceSystem & crs