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