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