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