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