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